twinkle-1.10.1/000077500000000000000000000000001277565361200132655ustar00rootroot00000000000000twinkle-1.10.1/.gitignore000066400000000000000000000010021277565361200152460ustar00rootroot00000000000000# generated by cmake *.cmake !cmake/*.cmake *.depends CMakeFiles Makefile /CMakeCache.txt /twinkle_config.h /twinkle.desktop # generated by build *.o *.qm ui_*.h moc_*.cpp src/twinkle-console src/gui/twinkle src/gui/twinkle_automoc.cpp src/gui/qrc_icons.cpp src/gui/qrc_icons.cxx src/gui/qrc_qml.cpp src/gui/qrc_qml.cxx src/parser/parser.cxx src/parser/parser.hxx src/parser/scanner.cxx src/sdp/sdp_parser.cxx src/sdp/sdp_parser.hxx src/sdp/sdp_scanner.cxx # Qt Creator files CMakeLists.txt.user* # generic *~ twinkle-1.10.1/.travis.yml000066400000000000000000000011511277565361200153740ustar00rootroot00000000000000language: cpp dist: trusty sudo: required env: - FLAGS="-DWITH_QT5=ON -DWITH_ALSA=ON -DWITH_GSM=ON -DWITH_SPEEX=ON -DWITH_ZRTP=ON" PACKAGES="libasound2-dev libgsm1-dev libspeex-dev libspeexdsp-dev libzrtpcpp-dev qtdeclarative5-dev qtquick1-5-dev qtscript5-dev qttools5-dev qttools5-dev-tools" install: - sudo apt-get update - sudo apt-get -y install bison cmake flex libccrtp-dev libmagic-dev libreadline-dev libsndfile1-dev libucommon-dev libxml2-dev linux-libc-dev $PACKAGES script: - mkdir _build - cd _build - cmake -DCMAKE_INSTALL_PREFIX=../_install $FLAGS .. - cmake --build . --target install twinkle-1.10.1/AUTHORS000066400000000000000000000021651277565361200143410ustar00rootroot00000000000000Author of Twinkle: Michel de Boer designed and implemented Twinkle. Lubos Dolezel ported Twinkle to Qt4/5 and took over further development. Contributions: * Werner Dittmann (ZRTP/SRTP) * Bogdan Harjoc (AKAv1-MD5, Service-Route) * Roman Imankulov (command line editing) * Ondrej Moris (codec preprocessing) * Rickard Petzall (ALSA) Twinkle contains the following 3rd party software packages: - GSM codec from Jutta Degener and Carsten Bormann see directory src/audio/gsm for more info - G.711/G.726 codec from Sun Microsystems see src/audio/README_G711 for more info - iLBC implementation from RFC 3951 (www.ilbcfreeware.org) - Parts of the STUN project from sourceforge http://sourceforge.net/projects/stun - Parts of libsrv at http://libsrv.sourceforge.net/ Dynamic linked libraries: - RTP, ZRTP and SRTP functionality is provided by the GNU ccRTP stack: http://www.gnu.org/software/ccrtp Translators: Czech Marek Straka Dutch Michel de Boer German Joerg Reisenweber French Olivier Aufrere Russian Michail Chodorenko Swedish Daniel Nylander Michel de Boer www.twinklephone.com twinkle-1.10.1/CMakeLists.txt000066400000000000000000000105511277565361200160270ustar00rootroot00000000000000project(twinkle) cmake_minimum_required(VERSION 2.6.0 FATAL_ERROR) set(PRODUCT_VERSION "1.10.1") set(PRODUCT_DATE "October 7, 2016") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") OPTION(WITH_ZRTP "Enable ZRTP encrypted calls" OFF) OPTION(WITH_SPEEX "Enable the Speex codec" OFF) OPTION(WITH_ILBC "Enable the iLBC codec" OFF) OPTION(WITH_ALSA "Enable ALSA support" ON) OPTION(WITH_DIAMONDCARD "Enable Diamondcard integration" OFF) OPTION(WITH_QT5 "Enable Qt 5 GUI" ON) OPTION(WITH_G729 "Enable G.729A support" OFF) OPTION(WITH_GSM "Use external GSM library" OFF) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") include (CheckIncludeFile) include (CheckIncludeFiles) find_package(LibXml2 REQUIRED) find_package(LibMagic REQUIRED) find_package(LibSndfile REQUIRED) find_package(Readline REQUIRED) find_package(BISON REQUIRED) find_package(FLEX REQUIRED) find_package(Ucommon REQUIRED) find_package(Commoncpp REQUIRED) find_package(Ccrtp REQUIRED) if (WITH_QT5) find_package(Qt5Widgets REQUIRED) find_package(Qt5LinguistTools REQUIRED) find_package(Qt5Quick REQUIRED) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS} ${Qt5Quick_EXECUTABLE_COMPILE_FLAGS}") include_directories(${Qt5Widgets_INCLUDES} ${Qt5Quick_INCLUDES}) add_definitions(${Qt5Widgets_DEFINITIONS} ${Qt5Quick_DEFINITIONS}) endif (WITH_QT5) include_directories(${LIBXML2_INCLUDE_DIR}) if (WITH_ALSA) find_package(ALSA) if (ALSA_FOUND) message(STATUS "libasound OK") set(HAVE_LIBASOUND TRUE) else (ALSA_FOUND) message(FATAL_ERROR "libasound not found!") endif (ALSA_FOUND) endif (WITH_ALSA) if (WITH_ZRTP) find_package(Zrtpcpp) if (ZRTPCPP_FOUND) message(STATUS "libzrtpcpp OK") set(HAVE_ZRTP TRUE) include_directories(${ZRTPCPP_INCLUDE_DIR}) else (ZRTPCPP_FOUND) message(FATAL_ERROR "libzrtpcpp not found!") endif (ZRTPCPP_FOUND) endif (WITH_ZRTP) if (WITH_SPEEX) find_package(Speex) if (SPEEX_FOUND) message(STATUS "Speex OK") set(HAVE_SPEEX TRUE) include_directories(${SPEEX_INCLUDE_DIR}) else (SPEEX_FOUND) message(FATAL_ERROR "Speex not found!") endif (SPEEX_FOUND) endif (WITH_SPEEX) if (WITH_ILBC) find_package(Ilbc) if (ILBC_FOUND) message(STATUS "iLBC OK") set(HAVE_ILBC TRUE) include_directories(${ILBC_INCLUDE_DIR}) else (ILBC_FOUND) message(FATAL_ERROR "iLBC not found!") endif (ILBC_FOUND) endif (WITH_ILBC) if (WITH_G729) find_package(G729) if (G729_FOUND) message(STATUS "bcg729 OK") set(HAVE_BCG729 TRUE) include_directories(${G729_INCLUDE_DIR}) else (G729_FOUND) message(FATAL_ERROR "bcg729 not found!") endif (G729_FOUND) endif (WITH_G729) if (WITH_GSM) find_package(Gsm) if (GSM_FOUND) message(STATUS "gsm OK") set(HAVE_GSM TRUE) include_directories(${GSM_INCLUDE_DIR}) else (GSM_FOUND) message(FATAL_ERROR "gsm not found!") endif (GSM_FOUND) endif (WITH_GSM) check_include_file(unistd.h HAVE_UNISTD_H) check_include_file(linux/types.h HAVE_LINUX_TYPES_H) check_include_files("sys/socket.h;linux/errqueue.h" HAVE_LINUX_ERRQUEUE_H) set(datadir "${CMAKE_INSTALL_PREFIX}/share/twinkle") configure_file(twinkle_config.h.in twinkle_config.h) configure_file(twinkle.desktop.in twinkle.desktop) include_directories("${CMAKE_BINARY_DIR}") install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/data/providers.csv ${CMAKE_CURRENT_SOURCE_DIR}/data/ringtone.wav ${CMAKE_CURRENT_SOURCE_DIR}/data/ringback.wav ${CMAKE_CURRENT_SOURCE_DIR}/src/gui/images/twinkle16.png ${CMAKE_CURRENT_SOURCE_DIR}/src/gui/images/twinkle32.png ${CMAKE_CURRENT_SOURCE_DIR}/src/gui/images/twinkle48.png DESTINATION share/twinkle) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/gui/images/twinkle48.png DESTINATION share/pixmaps RENAME twinkle.png) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/twinkle.desktop DESTINATION share/applications) install(FILES src/gui/images/twinkle16.png RENAME twinkle.png DESTINATION share/icons/hicolor/16x16/apps) install(FILES src/gui/images/twinkle24.png RENAME twinkle.png DESTINATION share/icons/hicolor/24x24/apps) install(FILES src/gui/images/twinkle32.png RENAME twinkle.png DESTINATION share/icons/hicolor/32x32/apps) install(FILES src/gui/images/twinkle48.png RENAME twinkle.png DESTINATION share/icons/hicolor/48x48/apps) install(FILES data/twinkle.svg DESTINATION share/icons/hicolor/scalable/apps) install(FILES data/twinkle.1 DESTINATION share/man/man1) add_subdirectory(src) twinkle-1.10.1/COPYING000066400000000000000000000432541277565361200143300ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. twinkle-1.10.1/Doxyfile000066400000000000000000001453151277565361200150040ustar00rootroot00000000000000# Doxyfile 1.5.0 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = Twinkle # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = doc # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Finnish, French, German, Greek, Hungarian, # Italian, Japanese, Japanese-en (Japanese with English messages), Korean, # Korean-en, Lithuanian, Norwegian, Polish, Portuguese, Romanian, Russian, # Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian. OUTPUT_LANGUAGE = English # This tag can be used to specify the encoding used in the generated output. # The encoding is not always determined by the language that is chosen, # but also whether or not the output is meant for Windows or non-Windows users. # In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES # forces the Windows encoding (this is the default for the Windows binary), # whereas setting the tag to NO uses a Unix-style encoding (the default for # all platforms other than Windows). USE_WINDOWS_ENCODING = NO # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = YES # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like the Qt-style comments (thus requiring an # explicit @brief command for a brief description. JAVADOC_AUTOBRIEF = YES # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the DETAILS_AT_TOP tag is set to YES then Doxygen # will output the detailed description near the top, like JavaDoc. # If set to NO, the detailed description appears after the member # documentation. DETAILS_AT_TOP = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for Java. # For instance, namespaces will be presented as packages, qualified scopes # will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to # include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = YES # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from the # version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = src src/utils src/im src/presence src/patterns # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py FILE_PATTERNS = *.cpp *.h # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = NO # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = src/twinkle_config.h # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES (the default) # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = YES # If the REFERENCES_RELATION tag is set to YES (the default) # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = YES # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. Otherwise they will link to the documentstion. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = YES # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = t_ #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compressed HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # If the GENERATE_TREEVIEW tag is set to YES, a side panel will be # generated containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, # Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are # probably better off using the HTML help feature. GENERATE_TREEVIEW = NO # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = NO # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = NO # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. This is useful # if you want to understand what is going on. On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = YES # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = NO # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will # generate a call dependency graph for every global function or class method. # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable call graphs for selected # functions only using the \callgraph command. CALL_GRAPH = NO # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then doxygen will # generate a caller dependency graph for every global function or class method. # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable caller graphs for selected # functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width # (in pixels) of the graphs generated by dot. If a graph becomes larger than # this value, doxygen will try to truncate the graph, so that it fits within # the specified constraint. Beware that most browsers cannot cope with very # large images. MAX_DOT_GRAPH_WIDTH = 1024 # The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height # (in pixels) of the graphs generated by dot. If a graph becomes larger than # this value, doxygen will try to truncate the graph, so that it fits within # the specified constraint. Beware that most browsers cannot cope with very # large images. MAX_DOT_GRAPH_HEIGHT = 1024 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that a graph may be further truncated if the graph's # image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH # and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), # the graph is not depth-constrained. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, which results in a white background. # Warning: Depending on the platform used, enabling this option may lead to # badly anti-aliased labels on the edges of a graph (i.e. they become hard to # read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::additions related to the search engine #--------------------------------------------------------------------------- # The SEARCHENGINE tag specifies whether or not a search engine should be # used. If set to NO the values of all tags below this one will be ignored. SEARCHENGINE = NO twinkle-1.10.1/NEWS000066400000000000000000001001221277565361200137600ustar00rootroot000000000000007 October 2016 - 1.10.1 ======================= - Stability fixes. - French translation updates. - Remove obsolete commoncpp2 dependency. - Set PA role to phone to pause music playback automatically. 15 July 2016 - 1.10.0 ======================= - Supports newest Qt5, drops support for Qt4. - Source code cleanups, spelling error fixes. - Handle digest authentication scheme case insensitively. - Address book UI fixes. - Fix build with ucommon 7.0. - Call history UI fixes. - Support using an external GSM library. - Various other bug fixes. 10 July 2015 - 1.9.0 ======================= - Ported to Qt4 and Qt5. - Added in-call OSD. - Added a new incoming call notification screen/dialog. - Support the G.729A codec. - Made Diamondcard support optional. - Save window geometry and state. - Visual feedback (button presses) in DTMF window when pushing keyboard buttons. - Support clipboard paste in DTMF window. - Removed Boost Regex dependency. - Eliminated some GUI freezes/stalls. 25 february 2009 - 1.4.2 ======================== - Integration with Diamondcard Worldwide Communication Service (worldwide calls to regular and cell phones and SMS). - Show number of calls and total call duration in call history. - Show message size while typing an instant message. - Show "referred by" party for an incoming transferred call in systray popup. - Option to allow call transfer while consultation call is still in progress. - Improved lock file checking. No more stale lock files. Bug fixes: ---------- - Opening an IM attachment did not work anymore. Build fixes: ------------ - Link with ncurses library 31 january 2009 - 1.4.1 ======================= Bug fixes: ---------- - No sound when Twinkle is compiled without speex support. Build fixes: ------------ - Compiling without KDE sometimes failed (cannot find -lqt-mt). - Configure script did not correctly check for the readline-devel package. 25 january 2009 - 1.4 ===================== - Service route discovery during registration. - Codec preprocessing: automatic gain control, voice activation detection, noise reduction, acoustic echo cancellation (experimental). - Support tel-URI as destination address for a call or instant message. - User profile option to expand a telephone number to a tel-URI instead of a sip-URI. - Add descending q-value to contacts in 3XX responses for the redirection services. - AKAv1-MD5 authentication. - Command line editing, history, auto-completion. - Ignore wrong formatted domain-parameter in digest challenge. - Match tel-URI in incoming call to address book. - Determine RTP IP address for SDP answer from RTP IP address in SDP offer. - Show context menu's when pressing the right mouse button instead of after clicking. - Swedish translation - Resampled ringback tone from 8287 Hz to 8000 Hz Bug fixes --------- - Text line edit in the message form looses focus after sending an IM. - Twinkle does not escape reserved symbols when dialing. - Deregister all function causes a crash. - Twinkle crashes at startup in CLI mode. - Twinkle may freeze when an ALSA error is detected when starting the ringback tone and the outgoing call gets answered very fast. - User profile editor did not allow spaces in a user name. New RFC's --------- RFC 3608 - Session Initiation Protocol (SIP) Extension Header Field for Service Route Discovery During Registration 24 august 2008 - 1.3.2 ====================== - Fix in non-KDE version for gcc 4.3 23 august 2008 - 1.3.1 ====================== - Disable file attachment button in message window when destination address is not filled in - Updated russian translation Build fixes ----------- - Fixes for gcc 4.3 (missing includes) - non-KDE version failed to build 18 august 2008 - 1.3 ==================== - Send file attachment with instant message. - Show timestamp with instant messages. - Instant message composition indication (RFC 3994). - Persistent TCP connections with keep alive. - Do not try to send SIP messages larger than 64K via UDP. - Integration with libzrtcpp-1.3.0 - Xsession support to restore Twinkle after system shutdown/startup. - Call snd_pcm_state to determine jitter buffer exhaustion (some ALSA implementations gave problems with the old method). - SDP parser allows SDP body without terminating CRLF. - Russian translation. Bug fixes --------- - SIP parser did not allow white space between header name and colon. - With "send in-dialog requests to proxy" enabled and transport mode set to "auto", in-dialog requests are wrongly sent via TCP. - Crash when a too large message is received. - Comparison of authentication parameters (e.g. algorithm) were case-sensitive. These comparisons must be case-insensitive. - SDP parser could not parse other media transports than RTP/AVP. - Twinkle sent 415 response instead of 200 OK on in-dialog INFO without body. - Twinkle responds with 513 Message too large on an incoming call. - ICMP error on STUN request causes Twinkle to crash. - Add received-parameter to Via header of an incoming request if it contains an empty rport parameter (RFC 3581) - Twinkle did not add Contact header and copy Record-Route header to 180 response. New RFC's --------- RFC 3994 - Indication of Message Composition for Instant Messaging 8 march 2008 - 1.2 ================== - SIP over TCP - Automatic selection of IP address. * On a multi-homed machine you do not have to select an IP address/NIC anymore. - Support for sending a q-value in a registration contact. - Send DTMF on an early media stream. - Choose auth over auth-int qop when server supports both for authentication. This avoids problems with SIP ALGs. - Support tel-URI in From and To headers in incoming SIP messages. - Print a log rotation message at end of log when a log file is full. - Remove 20 character limit on profile names. - Reject an incoming MESSAGE with 603 if max. sessions == 0 - Delivery notification when a 202 response is received on a MESSAGE. Bug fixes --------- - When you deactivate a profile that has MWI active, but MWI subscription failed, and subsequently activate this profile again, then Twinkle does not subscribe to MWI. - The max redirection value was always set to 1. - Leading space in the body of a SIP message causes a parse failure - Twinkle crashes with SIGABRT when it receives an INVITE with a CSeq header that contains an invalid method. - Latest release of lrelease corrupted translation files. - Twinkle crashes on 'twinkle --cmd line' - If an MWI NOTIFY does not contain a voice msg summary, twinkle shows a random number for the amount of messages waiting. - Depending on the locale Twinkle encoded a q-value with a comma instead of a dot as decimal point. Build changes ------------- - Modifications for gcc 4.3. - Remove fast sequence of open/close calls for ALSA to avoid problems with bluez. 21 july 2007 - 1.1 ================== - French translation - Presence - Instant messaging - New CLI commands: presence, message Bug fixes --------- - If a session was on-hold and Twinkle received a re-INVITE without SDP, it would offer SDP on-hold in the 200 OK, instead of a brand new SDP offer. - Twinkle refused to change to another profile with the same user name as the current active profile. - ICMP processing did not work most times (uninitialized data). - Replace strerror by strerror_r (caused rare SIGSEGV crashes) - Fix deadlock in timekeeper (caused rare freezes) New RFC's --------- RFC 3428 - Session Initiation Protocol (SIP) Extension for Instant Messaging RFC 3856 - A Presence Event Package for the Session Initiation Protocol (SIP) RFC 3863 - Presence Information Data Format (PIDF) RFC 3903 - Session Initiation Protocol (SIP) Extension for Event State Publication 19 may 2007 - 1.0.1 =================== - Czech translation - Check on user profiles having the same contact name at startup. - When comparing an incoming INVITE request-URI with the contact-name, ignore the host part to avoid NAT problems. - A call to voice mail will not be attached to the "redial" button. - Added voice mail entry to services and systray menu. - New command line options: --show, --hide - TWINKLE_LINE environment variable in scripts. This variable contains the line number (starting at 1) associated with a trigger. - Preload KAddressbook at startup. - Allow multiple occurrences of the display_msg parameter in the incoming call script to create multi-line messages. - Handle SIP forking and early media interaction Bug fixes --------- - Fix conference call - If lock file still exists when you start Twinkle, Twinkle asks if it should start anyway. When you click 'yes', Twinkle does not start. - Audio validation opened soundcard in stereo instead of mono - When quitting Twinkle while the call history window is open, a segfault occurs - When an incoming call is rejected when only unsupported codecs are offered, it does not show as a missed call in the call history. - Segfault when the remote party establishes an early media session without sending a to-tag in the 1XX response (some Cisco devices). - in_call_failed trigger was not called when the call failed before ringing. - Escape double quote with backslash in display name. - On some system Twinkle occasionally crashed at startup with the following error: Xlib: unexpected async reply Build Changes ------------- - Remove AC_CHECK_HEADERS([]) from configure.in - Configure checks for lrelease. Other ----- - Very small part of the comments has been formatted now for automatic documentation generation with doxygen. 22 jan 2007 - 1.0 ================= - Local address book - Message waiting indication (MWI) * Sollicted MWI as specified by RFC 3842 * Unsollicited MWI as implemented by Asterisk - Voice mail speed dial - Call transfer with consultation * This is a combination of a consultation call on the other line followed by a blind transfer. - Attended call transfer * This is a combination of a consultation call on the other line followed by a replacement from B to C of the call on the first line. This is only possible if the C-party supports "replaces". If "replaces" is not supported, then twinkle automatically falls back to "transfer with consultation". - User identity hiding - Multi language support This version contains Dutch and German translations - Send BYE when a CANCEL/2XX INVITE glare occurs. - When call release was not immediate due to network problems or protocol errors, the line would be locked for some time. Now Twinkle releases a call in the background immediately freeing the line for new calls. - Escape reserved symbols in a URI by their hex-notation (%hex). - Changed key binding for Bye from F7 to ESC - When a lock file exists at startup, Twinkle asks if you want to override it - New command line options: --force, --sip-port, --rtp-port - Ring tone and speaker device list now also shows playback only devices - Microphone device list now also shows capture only devices - Validate audio device settings on startup, before making a call, before answering a call. - SIP_FROM_USER, SIP_FROM_HOST, SIP_TO_USER, SIP_TO_HOST variables for call scripts. - display_msg parameter added to incoming call script - User profile options to indicate which codec preference to follow - Twinkle now asks permission for an incoming REFER asynchronously. This prevents blocking of the transaction layer. - Highlight missed calls in call history - Support for G.726 ATM AAL2 codeword packing - replaces SIP extension (RFC 3891) - norefesub SIP extension (RFC 4488) - SIP parser supports IPv6 addresses in SIP URI's and Via headers (Note: Twinkle does not support transport over IPv6) - Support mid-call change of SSRC - Handling of SIGCHLD, SIGTERM and SIGINT on platforms implementing LinuxThreads instead of NPTL threading (e.g. sparc) Bug fixes --------- - Invalid speex payload when setting ptime=30 for G.711 - When editing the active user profile via File -> Change User -> Edit QObject::connect: No such slot MphoneForm::displayUser(t_user*) - 32 s after call setup the DTMF button gets disabled. - 4XX response on INVITE does not get properly handled. From/To/Subject labels are not cleared. No call history record is made. - The dial combobox accepted a newline through copy/past. This corrupted the system settings. - When a far-end responds with no supported codecs, Twinkle automatically releases the call. If the far-end sends an invalid response on this release and the user pressed the BYE button, Twinkle crashed. - When using STUN the private port was put in the Via header instead of the public port. - Twinkle crashes once in a while, while it is just sitting idle. Build changes ------------- - If libbind exists then link with libbind, otherwise link with libresolv This solves GLIBC_PRIVATE errors on Fedora - Link with libboost_regex or libboost_regex-gcc New RFC's --------- RFC 3323 - A Privacy Mechanism for the Session Initiation Protocol (SIP) RFC 3325 - Private Extensions to the Session Initiation Protocol (SIP) for Asserted Identity within Trusted Networks RFC 3842 - A Message Summary and Message Waiting Indication Event Package for the Session Initiation Protocol (SIP) RFC 3891 - The Session Initiation Protocol (SIP) "Replaces" Header RFC 4488 - Suppression of Session Initiation Protocol (SIP) REFER Method Implicit Subscription 01 oct 2006 - Release 0.9 ========================= - Supports Phil Zimmermann's ZRTP and SRTP for secure voice communication. ZRTP/SRTP is provided by the latest version (1.5.0) of the GNU ccRTP library. The implementation is interoperable with Zfone beta2 - SIP INFO method (RFC 2976) - DTMF via SIP INFO - G.726 codec (16, 24, 32 and 48 kbps modes) - Option to hide display - CLI command "answerbye" to answer an incoming or hangup an established call - Switch lines from system tray menu - Answer or reject a call from the KDE systray popup on incoming call - Icons to indicate line status - Default NIC option in system settings - Accept SDP offer without m= lines (RFC 3264 section 5, RFC 3725 flow IV) Bug fixes --------- - t_audio::open did not return a value - segmentation fault when quitting Twinkle in transient call state - Twinkle did not accept message/sipfrag body with a single CRLF at the end - user profile could not be changed on service redirect dialog - Twinkle did not react to 401/407 authentication challenges for PRACK, REFER, SUBSCRIBE and NOTIFY Build changes ------------- - For ZRTP support you need to install libzrtpcpp first. This library comes as an extension library with ccRTP. 09 jul 2006 - Release 0.8.1 =========================== - Removed iLBC source code from Twinkle. To use iLBC you can link Twinkle with the ilbc library (ilbc package). When you have the ilbc library installed on your system, then Twinkle's configure script will automatically setup the Makefiles to link with the library. Bug fixes --------- - Name and photo lookups in KAddressbook on incoming calls may freeze Twinkle. Build improvements ------------------ - Added missing includes to userprofile.ui and addressfinder.h - Configure has new --without-speex option 01 jul 2006 - Release 0.8 ========================= - iLBC - Make supplementary service settings persistent - Lookup name in address book for incoming call - Display photo from address book of caller on incoming call - Number conversion rules - Always popup systray notification (KDE only) on incoming call - Add organization and subject to incoming call popup - New call script trigger points: incoming call answered, incoming call failed, outgoing call, outgoing call answered, outgoing call failed, local release, remote release. - Added 'end' parameter for the incoming call script - Option to provision ALSA and OSS devices that are not in the standard list of devices. - Option to auto show main window on incoming call - Resized the user profile window such that it fits on an 800x600 display - Popup the user profile selection window, when the SIP UDP port is occupied during startup of Twinkle, so the user can change to another port. - Skip unsupported codecs in user profile during startup Bug fixes --------- - Sometimes the NAT discovery window never closed - When RTP timestamps wrap around some RTP packets may be discarded - When the dial history contains an entry of insane length, the main window becomes insanely large on next startup - On rare occasions, Twinkle could respond to an incoming call for a deactivated user profile. - Credentials cache did not get erased when a failure response other than 401/407 was received on a REGISTER with credentials. - G.711 enocders amplified soft noise from the microphone. Newly supported RFC's --------------------- RFC 3951 - Internet Low Bit Rate Codec (iLBC) RFC 3952 - Real-time Transport Protocol (RTP) Payload Format for internet Low Bit Rate Codec (iLBC) Speech Build notes ----------- - New dependency on libboost-regex (boost package) 07 may 2006 - Release 0.7.1 =========================== - Check that --call and --cmd arguments are not empty - When DTMF transport is "inband", then do not signal RFC2833 support in SDP Bug fixes --------- - CLI and non-KDE version hang when stopping ring tone - The GUI allowed payload type 96-255 for DTMF and Speex, while maximum value is only 127 - When a dynamic codec change takes place at the same time as a re-INVITE Twinkle sometimes freezes. - Sending RFC 2833 DTMF events fails when codec is speex-wb or speex-uwb 29 apr 2006 - Release 0.7 ========================= - Speex support (narrow, wide and ultra wide band) - Support for dynamic payload numbers for audio codecs in SDP - Inband DTMF (option for DTMF transport in user profile) - UTF-8 support to properly display non-ASCII characters - --cmd command line option to remotely execute CLI commands - --immediate command line option to perform --call and --cmd without user confirmation. - --set-profile command line option to set the active profile. - Support "?subject=" as part of address for --call - The status icon are always displayed: gray -> inactive, full color -> active - Clicking the registration status icon fetches current registration status - Clicking the service icons enables/disables the service - Fancier popup from KDE system tray on incoming call. - Popup from system tray shows as long as the phone is ringing. - Reload button on address form - Remove special phone number symbols from dialed strings. This option can be enabled/disabled via the user profile. - Remove duplicate entries from the dial history drop down box - Specify in the user profile what symbols are special symbols to remove. - Changed default for "use domain to create unique contact header value" to "no" - New SIP protocol option: allow SDP change in INVITE responses - Do not ask username and password when authentication for an automatic re-regsitration fails. The user may not be at his desk, and the authentication dialog stalls Twinkle. - Ask authentication password when user profile contains authentication name, but no password. - Improved handling of socket errors when interface goes down temporarily. Bug fixes --------- - If the far end holds a call and then resumes a call while Twinkle has been put locally on-hold, then Twinkle will start recording sound from the mic and send it to the far-end while indicating that the call is still on-hold. - Crash on no-op SDP in re-INVITE - Twinkle exits when it receives SIGSTOP followed by SIGCONT - call release cause in history is incorrect for incoming calls. Build improvements ------------------ - Break dependency on X11/xpm.h 26 feb 2006 - Release 0.6.2 =========================== - Graceful termination on reception of SIGINT and SIGTERM Bug fixes --------- - If the URI in a received To-header is not enclosed by '<' and '>', then the tag parameter is erronesouly parsed as a URI parameter instead of a header parameter. This causes failing call setup, tear down, when communicating with a far-end that does not enclose the URI in angle brackets in the To-header. - Function to flush OSS buffers flushed a random amount of samples that could cause sound clipping (at start of call and after call hold) when using OSS. - In some cases Twinkle added "user=phone" to a URI when the URI already had a user parameter. 11 feb 2006 - Release 0.6.1 =========================== - action=autoanswer added to call script actions - Performance improvement of --call parameter - Synchronized dial history drop downs on main window and call dialog - Dial history drop down lists are stored persistently - Redial information is stored persistently Bug fixes --------- - When using STUN Twinkle freezes when making a call and the STUN server does not respond within 200 ms (since version 0.2) - Some malformed SIP messages triggered a memory leak in the parser code generated by bison (since version 0.1) - The lexical scanner jammed on rubbish input (since version 0.1) 05 feb 2006 - Release 0.6 ========================= - Custom ring tones (package libsndfile is needed) - Twinkle can call a user defineable script for each incoming call. With this script the user can: * reject, redirect or accept a call * define a specific ring tone (distinctive ringing) - Missed call indication - Call directly from the main window - DTMF keys can by typed directly from the keyboard at the main window. Letters are converted to the corresponding digits. - Letters can be typed in the DTMF window. They are converted to digits. - Call duration in call history - Call duration timer while call is established - Added --call parameter to command line to instruct Twinkle to make a call - Increased expiry timer for outgoing RTP packets to 160 ms With this setting slow sound cards should give better sound quality for the mic. - System setting to disable call waiting. - System setting to modify hangup behaviour of 3-way call. Hang up both lines or only the active line. - Replace dots with underscores in contact value - Silently discard packets on the SIP port smaller than 10 bytes - User profile option to disable the usage of the domain name in the contact header. - Graceful release of calls when quitting Twinkle - Changed call hold default from RFC2543 to RFC3264 Bug fixes --------- - An '=' in a value of a user profile or system settings parameter caused a syntax error - If a default startup profile was renamed, the default startup list was not updated - When call was put on-hold using RFC2543 method, the host in the SDP o= line was erroneously set to 0.0.0.0 - When a response with wrong tags but correct branch was received, a line would hang forever (RFC 3261 did not specify this scenario). - If far end responds with 200 OK to CANCEL, but never sends 487 on INVITE as mandated by RFC 3261, then a line would hang forever - CPU load was sometimes excessive when using ALSA 01 jan 2006 - Release 0.5 ========================= - Run multiple user profiles in parallel - Add/remove users while Twinkle is running - The SIP UDP port and RTP port settings have been moved from the user profile to system settings. Changes of the default values in the user profile will be lost. - DNS SRV support for SIP and STUN - ICMP processing - SIP failover on 503 response - SIP and STUN failover on ICMP error - When a call is originated from the call history, copy the subject to the call window (prefixed with "Re:" when replying to a call). - Remove '/' from a phone number taken from KAddressbook. / is used in Germany to separate the area code from the local number. - Queue incoming in-dialog request if ACK has not been received yet. - Clear credentials cache when user changes realm, username or password - Added micro seconds to timestamps in log - Detecting a soundcard playing out at slightly less than 8000 samples per second is now done on the RTP queue status. This seems to be more reliable than checking the ALSA sound buffer filling. - OSS fragment size and ALSA period size are now changeable via the system settings. Some soundcard problems may be solved by changing these values. - Default ALSA period size for capturing lowered from 128 to 32. This seems to give better performance on some sound cards. Bug fixes --------- - With certain ALSA settings (eg. mic=default, speaker=plughw), the ALSA device got locked up after 1 call. - The ports used for NAT discovery via STUN stayed open. - When a STUN transaction for a media port failed, the GUI did not clear the line information fields. - Sending DTMF events took many unnecessary CPU cycles - Parse failure when Server or User-Agent header contained comment only Newly supported RFC's --------------------- RFC 2782 - A DNS RR for specifying the location of services (DNS SRV) RFC 3263 - Session Initiation Protocol (SIP): Locating SIP Servers 28 nov 2005 - Release 0.4.2 =========================== - Microphone noise reduction (can be disabled in system settings) - System tray icon shows status of active line and enabled services - Call history option added to system tray menu Bug fixes --------- - Twinkle crashes at startup when the systray icon is disabled in the system settings. - Line stays forever in dialing state when pressing ESC in the call window 19 nov 2005 - Release 0.4.1 =========================== - Fixed build problems with gcc-4.0.2 and qt3-3.4.4 18 nov 2005 - Release 0.4 ========================= - Interface to KAddressbook - History of incoming and outgoing calls (successful and missed calls) - History of 10 last calls on call dialog window for redialling - Call and service menu options added to KDE sys tray icon - Allow a missing mandatory Expires header in a 2XX response on SUBSCRIBE - Big Endian support for sound playing (eg. PPC platforms) - System setting to start Twinkle hidden in system tray - System setting to start with a default profile - System setting to start on a default IP address - Command line option (-i) for IP address Bug fixes --------- - send a 500 failure response on a request that is received out of order instead of discarding the request. - 64bit fix in events.cpp - race condition on starting/stopping audio threads could cause a crash - segmentation fault when RTP port could not be opened. - CLI looped forever on reaching EOF - 64bit fix in events.cpp - ALSA lib pcm_hw.c:590:(snd_pcm_hw_pause) SNDRV_PCM_IOCTL_PAUSE failed - sometimes when quitting Twinkle a segmentation fault occurred Build improvements ------------------ - Removed platform dependent code from stunRand() in stun.cxx - It should be possible to build Twinkle without the KDE addons on a non-KDE system - new option --without-kde added to configure to build a non-KDE version of Twinkle 22 oct 2005 - Release 0.3.2 =========================== - Fixed several build problams with KDE include files and libraries. If you already successfully installed release 0.3.1 then there is no need to upgrade to 0.3.2 as there is no new functionality. 16 oct 2005 - Release 0.3.1 =========================== This is a minor bug fix release. Bug fixes: ---------- - Command line options -f and -share were broken in release 0.3 This release fixes the command line options. 09 oct 2005 - Release 0.3 ========================= New functionality: ------------------ - ALSA support - System tray icon - Send NAT keep alive packets when Twinkle sits behind a symmetric firewall (discovered via STUN) - Allow missing or wrong Contact header in a 200 OK response on a REGISTER Bug fixes: ---------- - Hostnames in Via and Warning headers were erroneously converted to lower case. - t_ts_non_invite::timeout assert( t==TIMER_J ) when ACK is received for a non-INVITE request that had INVITE as method in the CSeq header. - The SIP/SDP parser accepted a port number > 65535. This caused an assert - Segmentation fault on some syntax errors in SIP headers - Line got stuck when CSeq sequence nr 0 was received. RFC 3261 allows 0. - With 100rel required, every 1XX after the first 1XX response were discarded. - Fixed build problems on 64-bit architectures. - Dead lock due to logging in UDP sender. - Segmentation fault when packet loss occurred while the sequence number in the RTP packets wrapped around. - Route set was not recomputed on reception of a 2XX response, when a 1XX repsonse before already contained a Record-Route header. 30 jul 2005 - Release 0.2.1 =========================== New functionality: ------------------ - Clear button on log view window. Bug fixes: ---------- - The system settings window confused the speaker and mic settings. - Log view window sometimes opened behind other windows. - Segmentation fault when SUBSCRIBE with expires=0 was received to end a refer subscription. - When a call transfer fails, the original call is received. If the line for this call is not the active call however, the call should stay on-hold. - On rare occasions a segmentation fault occurred when the ring tone was stopped. - Log view window sometimes caused deadlock. 24 jul 2005 - Release 0.2 ========================= New functionality: ------------------ - STUN support for NAT traversal - Blind call transfer service - Reject call transfer request - Auto answer service - REFER, NOTIFY and SUBSCRIBE support for call transfer scenario's * REFER is sent for blind call transfer. Twinkle accpets incoming NOTIFY messages about the transfer progress. Twinkle can send SUBSCRIBE to extend refer event subscription * Incoming REFER within dialog is handled by Twinkle Twinkle sends NOTIFY messages during transfer. Incoming SUBSCRIBE to extend refer event subscription is granted. - Retry re-INVITE after a glare (491 response, RFC 3261 14.1) - Respond with 416 if a request with a non-sip URI is received - Multiple sound card support for playing ring tone to a different device than speech - The To-tag in a 200 OK on a CANCEL was different than the To-tag in a provisional response on the INVITE. RFC 3261 recommends that these To-tags are the same. Twinkle now uses the same To-tag. - Show error messages to user when trying to submit invalid values on the user profile - DTMF volume configurable via user profile - Log viewer - User profile wizard - Help texts for many input fields (e.g. in user profile). Help can be accessed by pressing Ctrl+F1 or using the question mark from the title bar. Bug fixes: ---------- - A retransmission of an incoming INVITE after a 2XX has been sent was seen as a new INVITE. - If an OPTIONS request timed out then the GUI did not release its lock causing a deadlock. - If the URI in a To, From, Contact or Reply-To header is not enclosed by < and >, then the parameters (separated by a semi-colon) belong to the header, NOT to the URI. They were parsed as parameters of the URI. This could cause the loss of a tag-parameter causing call setup failures. - Do not resize window when setting a long string in to, from or subject Newly supported RFC's --------------------- RFC 3265 - Session Initiation Protocol (SIP)-Specific Event Notification RFC 3420 - Internet Media Type message/sipfrag RFC 3489 - Simple Traversal of User Datagram Protocol (UDP) Through Network Address Translators (NATs) RFC 3515 - The Session Initiation Protocol (SIP) Refer Method RFC 3892 - The Session Initiation Protocol (SIP) Referred-By Mechanism 27 apr 2005 - Release 0.1 ========================= First release of Twinkle, a SIP VoIP client. - Basic calls - 2 call appearances (lines) - Call Waiting - Call Hold - 3-way conference calling - Mute - Call redirection on demand - Call redirection unconditional - Call redirection when busy - Call redirection no answer - Reject call redirection request - Call reject - Do not disturb - Send DTMF digits to navigate IVR systems - NAT traversal through static provisioning - Audio codecs: G.711 A-law, G.711 u-law, GSM Supported RFC's --------------- - RFC 2327 - SDP: Session Description Protocol - RFC 2833 - RTP Payload for DTMF Digits - RFC 3261 - SIP: Session Initiation Protocol - RFC 3262 - Reliability of Provisional Responses in SIP - RFC 3264 - An Offer/Answer Model with the Session Description Protocol (SDP) - RFC 3581 - An extension to SIP for Symmetric Response Routing - RFC 3550 - RTP: A Transport Protocol for Real-Time Applications RFC 3261 is not fully implemented yet. - No TCP transport support, only UDP - No DNS SRV support, only DNS A-record lookup - Only plain SDP bodies are supported, no multi-part MIME or S/MIME - Only sip: URI support, no sips: URI support twinkle-1.10.1/README.md000066400000000000000000000226241277565361200145520ustar00rootroot00000000000000# Twinkle Twinkle is a SIP-based VoIP client. ## Dependencies To compile Twinkle you need the following libraries: * ucommon [GNU uCommon C++](http://www.gnu.org/software/commoncpp/) * ccRTP (version >= 1.5.0) [GNU RTP Stack](http://www.gnu.org/software/ccrtp/) * libxml2 * libsndfile * Qt 5 ### Optional dependencies * alsa-lib * libzrtpcpp (version >= 0.9.0) [ZRTP library, ccRTP support must be enabled](http://www.gnutelephony.org/index.php/GNU_ZRTP) * bcg729 [G.729A codec library](http://www.linphone.org/technical-corner/bcg729/overview) * speex [Speex codec library](http://www.speex.org/) * iLBC [iLBC codec library](http://www.ilbcfreeware.org/) ## Build First of all, choose which options you want to have enabled. All possible options are: * Qt 5 GUI: `-DWITH_QT5=On` (on by default) * ALSA support: `-DWITH_ALSA=On` (on by default) * ZRTP support: `-DWITH_ZRTP=On` * G.729A codec support: `-DWITH_G729=On` * Speex codec support: `-DWITH_SPEEX=On` * iLBC codec support: `-DWITH_ILBC=On` * Diamondcard support: `-DWITH_DIAMONDCARD=On` ### Build instructions # Create a subdirectory for the build an enter it mkdir build && cd build # Run cmake with a list of build options cmake .. -Dexample_option=On # Build Twinkle make # Install Twinkle make install ## Shared user data Installation will create the following directory for shared user data on your system: ${CMAKE_INSTALL_PREFIX}/share/twinkle The typical default value for `CMAKE_INSTALL_PREFIX` is `/usr/local`. ## Application icon If you want to create an application link on your desktop you can find an application icon in the shared user data directory: * twinkle16.png (16x16 icon) * twinkle32.png (32x32 icon) * twinkle48.png (48x48 icon) ## User data On first run Twinkle will create the `.twinkle` directory in your home directory. In this directory all user data will be put: * user profiles (.cfg) * log files (.log) * system settings (twinkle.sys) * call history (twinkle.ch) * lock file (twinkle.lck) ## Starting Twinkle Give the command: twinkle `twinkle -h` will show you some command line options you may use. NOTE: the CLI option is not fool proof. A command given at a wrong time may crash the program. It is recommended to use the GUI. If you do not specify a configuration file (`-f `) on the command line, then Twinkle will look for configuration files in your `.twinkle` directory. If you do not have any configuration file, the configuration file editor will startup so you can create one. If you have configuration files, then Twinkle lets you select an existing configuration file. See below for some hints on settings to be made with the profile configuration editor. If you specify a configuration file name, then Twinkle will such for this configuration file in your .twinkle directory. If you have put your configuration file in another location you have to specify the full path name for the file, i.e. starting with a slash. NOTE: the configuration file editor only exists in the GUI. If you run the CLI mode, you must have a configuration file. So first create a configuration file in GUI mode or hand edit a configuration file, before running the CLI mode. If you run the CLI mode and you do not specify a file name on the command line, then Twinkle will use twinkle.cfg ## NAT If there is a NAT between you and your SIP server then you have 3 options to make things work: 1. Your SIP provider uses a Session Border Controller 2. Your SIP provider offers a STUN server 3. Make static address mappings in your NAT for SIP and RTP STUN can be enabled in the NAT section of the user profile. For the static address mappings enable the following in the NAT section of the user profile: Use statically configured public IP address inside SIP messages And fill in the public IP address of your NAT. Twinkle will then use this IP address inside SIP headers and SDP bodies instead of the private IP address of your machine. In addition you have to add the following port forwardings for UDP on your NAT public:5060 --> private:5060 (for SIP signaling) public:8000 --> private:8000 (for RTP on line 1) public:8001 --> private:8001 (for RTCP on line 1) public:8002 --> private:8002 (for RTP on line 2) public:8003 --> private:8003 (for RTCP on line 2) public:8004 --> private:8004 (for RTP for call transfer) public:8005 --> private:8005 (for RTCP for call transfer) If you have changed the SIP/RTP ports in your profile you have to change the port forwarding rules likewise. ## Log files During execution Twinkle will create the following log files in your .twinkle directory: * twinkle.log (latest log file) * twinkle.log.old (previous log file) When twinkle.log is full (default is 5 MB) then it is moved to twinkle.log.old and a new twinkle.log is created. On startup an existing twinkle.log is moved to twinkle.log.old and a new twinkle.log is created. ## User profile configuration A user profile contains information about your user account, SIP proxy, and several SIP protocol options. If you use Twinkle with different user accounts you may create multiple user profiles. When you create a new profile you first give it a name and then you can make the appropriate settings. The name of the profile is what later on appears in the selection box when you start Twinkle again. Or you can give the name.cfg at the command line (-f option) to immediately start that profile. The user profile is stored as '.cfg' in the .twinkle directory where is the name you gave to the profile. At a minimum you have to specify the following: * User name: this is your SIP user name (eg. phone number) * Domain: the domain of your provider (eg. fwd.pulver.com) this could also be the IP address of your SIP proxy if you want to do IP-to-IP dialing (without proxy) then fill in the IP address or FQDN of your computer. If your SIP proxy does not request authentication and the value you filled in for 'Domain' can be resolved to an IP address by Twinkle, eg. it is an IP address or an FQDN that is in an A-record of the DNS, then you are ready now. ## Authentication If your proxy needs authentication, then specify the following fields in the SIP authentication box: * Realm: the realm for authentication you might leave the realm empty. If you do so, then Twinkle will use the name and password regardless of the realm put in the challenge by the proxy. For most network setups this is fine. You only need to explicitly specify a realm when you have call scenario's where you have to access multiple realms. Then for the realms not known to Twinkle you will be requested for a login when needed. * Name: your authentication name * Password: your authentication password If authentication fails during registration or any other SIP request because you filled in wrong values, then Twinkle will at that time interactively request your login and cache it. ## Outbound proxy An outbound proxy is only needed if the domain value cannot be resolved to an IP address by Twinkle or because your provider demands you to use an outbound proxy that is at a different IP address. Check the 'use outbound proxy' check box in the SIP server section. For outbound proxy fill in an IP address or an FQDN that can be resolved to an IP address via DNS. By default only out-of-dialog requests (eg. REGISTER, OPTIONS, initial INVITE) are sent to the outbound proxy. In-dialog requests (eg. re-INVITE, BYE) are sent to the target indicated by the far end during call setup. By checking 'send in-dialog requests to proxy' Twinkle will ignore this target and send these requests also to the proxy. Normally you would not need this. It could be useful in a scenario where the far-end indicates a target that cannot be resolved to an IP address. By checking "Do not send a request to proxy if its destination can be resolved locally" will make Twinkle always first try to figure out the destination IP address itself, i.e. based on the request-URI and Route-headers. Only when that fails the outbound-proxy will be tried, but only for the options checked above. I.e. if you did not check the 'in-dialog' option, then an in-dialog request will never go to the proxy. If its destination cannot be resolved, then the request will simply fail. ## Registrar By default a REGISTER will be send to the IP address resolved from the domain value or to the outbound proxy if specified. If your service provider has a dedicated registrar which is different from these IP addresses, then you can specify the IP or FQDN of the registrar in the registrar-field. The 'expiry' value is the expiry of your registration. Just before the registration expires Twinkle will automatically refresh the registration. The expiry time may be overruled by the registrar. The 'registrar at startup option' will make Twinkle automatically send a REGISTER on startup of the profile. ## Addressing When you invite someone to a call you have to enter an an address. A SIP address has the following form: sip:@ Where 'user' is a user name or a phone number and 'host-part' is a domain name, FQDN or IP address The only mandatory part for you to enter is the . Twinkle will fill in the other parts if you do not provide them. For the host-part, Twinkle will fill in the value you configured as your 'domain'. Currently `sip:` is the only addressing scheme supported by Twinkle. Michel de Boer [michel@twinklephone.com] Lubos Dolezel [lubos@dolezel.info] twinkle-1.10.1/THANKS000066400000000000000000000007601277565361200142030ustar00rootroot00000000000000Thanks to the following people for testing and finding all those lovely bugs: Richard Bos Schelte Bron Ruud Linders John van der Ploeg Marco van Zijl Thanks to Richard Bos for RPM building and advertising. Thanks to Joerg Reisenweber for his excellent testing and debugging. Thanks to Treeve Jelbert for helping me catching build errors in release 0.4 Thanks to James Le Cuirot for helping me debug several ALSA and RTP problems. Qt 4 & 5 porting done by Lubos Dolezel and Michal Kubecek. twinkle-1.10.1/TODO000066400000000000000000000012161277565361200137550ustar00rootroot00000000000000* make KDE support work again After cleaning up the autotools config, twinkle only builds with --without-kde. This is only temporary, the goal is to allow building against KDE4 libraries (and, preferrably, also KDE5). * IPv6 support Still missing, AFAIK. * t_gui locking Dirty workaround for (mostly) calling GUI related methods from non-GUI threads. Doesn't actually work with Qt4, causing segfaults. Analyze the remaining uses and get rid of it. * MEMMAN_* tracking macros Ugly and unreliable (too easy to forget adding them). Either replace them with overloaded new and delete operators or kill them completely and rely on valgrind. twinkle-1.10.1/cmake/000077500000000000000000000000001277565361200143455ustar00rootroot00000000000000twinkle-1.10.1/cmake/FindCcrtp.cmake000066400000000000000000000010631277565361200172230ustar00rootroot00000000000000FIND_PATH(CCRTP_INCLUDE_DIR ccrtp/rtp.h) FIND_LIBRARY(CCRTP_LIBRARIES NAMES ccrtp) IF(CCRTP_INCLUDE_DIR AND CCRTP_LIBRARIES) SET(CCRTP_FOUND TRUE) ENDIF(CCRTP_INCLUDE_DIR AND CCRTP_LIBRARIES) IF(CCRTP_FOUND) IF (NOT Ccrtp_FIND_QUIETLY) MESSAGE(STATUS "Found ccrtp includes: ${CCRTP_INCLUDE_DIR}/ccrtp/rtp.h") MESSAGE(STATUS "Found ccrtp library: ${CCRTP_LIBRARIES}") ENDIF (NOT Ccrtp_FIND_QUIETLY) ELSE(CCRTP_FOUND) IF (Ccrtp_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could NOT find ccrtp development files") ENDIF (Ccrtp_FIND_REQUIRED) ENDIF(CCRTP_FOUND) twinkle-1.10.1/cmake/FindCommoncpp.cmake000066400000000000000000000012211277565361200200770ustar00rootroot00000000000000FIND_PATH(COMMONCPP_INCLUDE_DIR commoncpp/config.h) FIND_LIBRARY(COMMONCPP_LIBRARIES NAMES commoncpp) IF(COMMONCPP_INCLUDE_DIR AND COMMONCPP_LIBRARIES) SET(COMMONCPP_FOUND TRUE) ENDIF(COMMONCPP_INCLUDE_DIR AND COMMONCPP_LIBRARIES) IF(COMMONCPP_FOUND) IF (NOT Commoncpp_FIND_QUIETLY) MESSAGE(STATUS "Found commoncpp includes: ${COMMONCPP_INCLUDE_DIR}/commoncpp/config.h") MESSAGE(STATUS "Found commoncpp library: ${COMMONCPP_LIBRARIES}") ENDIF (NOT Commoncpp_FIND_QUIETLY) ELSE(COMMONCPP_FOUND) IF (Commoncpp_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could NOT find commoncpp development files") ENDIF (Commoncpp_FIND_REQUIRED) ENDIF(COMMONCPP_FOUND) twinkle-1.10.1/cmake/FindG729.cmake000066400000000000000000000010511277565361200165750ustar00rootroot00000000000000FIND_PATH(G729_INCLUDE_DIR bcg729/decoder.h) FIND_LIBRARY(G729_LIBRARY NAMES bcg729) IF(G729_INCLUDE_DIR AND G729_LIBRARY) SET(G729_FOUND TRUE) ENDIF(G729_INCLUDE_DIR AND G729_LIBRARY) IF(G729_FOUND) IF (NOT G729_FIND_QUIETLY) MESSAGE(STATUS "Found bcg729 includes: ${G729_INCLUDE_DIR}/bcg729/decoder.h") MESSAGE(STATUS "Found bcg729 library: ${G729_LIBRARY}") ENDIF (NOT G729_FIND_QUIETLY) ELSE(G729_FOUND) IF (G729_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could NOT find bcg729 development files") ENDIF (G729_FIND_REQUIRED) ENDIF(G729_FOUND) twinkle-1.10.1/cmake/FindGsm.cmake000066400000000000000000000010021277565361200166670ustar00rootroot00000000000000FIND_PATH(GSM_INCLUDE_DIR gsm/gsm.h) FIND_LIBRARY(GSM_LIBRARY NAMES gsm) IF(GSM_INCLUDE_DIR AND GSM_LIBRARY) SET(GSM_FOUND TRUE) ENDIF(GSM_INCLUDE_DIR AND GSM_LIBRARY) IF(GSM_FOUND) IF (NOT Gsm_FIND_QUIETLY) MESSAGE(STATUS "Found gsm includes: ${GSM_INCLUDE_DIR}/gsm/config.h") MESSAGE(STATUS "Found gsm library: ${GSM_LIBRARY}") ENDIF (NOT Gsm_FIND_QUIETLY) ELSE(GSM_FOUND) IF (Gsm_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could NOT find gsm development files") ENDIF (Gsm_FIND_REQUIRED) ENDIF(GSM_FOUND) twinkle-1.10.1/cmake/FindIlbc.cmake000066400000000000000000000010551277565361200170220ustar00rootroot00000000000000FIND_PATH(ILBC_INCLUDE_DIR ilbc/iLBC_decode.h) FIND_LIBRARY(ILBC_LIBRARIES NAMES ilbc) IF(ILBC_INCLUDE_DIR AND ILBC_LIBRARIES) SET(ILBC_FOUND TRUE) ENDIF(ILBC_INCLUDE_DIR AND ILBC_LIBRARIES) IF(ILBC_FOUND) IF (NOT Ilbc_FIND_QUIETLY) MESSAGE(STATUS "Found ilbc includes: ${ILBC_INCLUDE_DIR}/ilbc/iLBC_decode.h") MESSAGE(STATUS "Found ilbc library: ${ILBC_LIBRARIES}") ENDIF (NOT Ilbc_FIND_QUIETLY) ELSE(ILBC_FOUND) IF (Ilbc_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could NOT find ilbc development files") ENDIF (Ilbc_FIND_REQUIRED) ENDIF(ILBC_FOUND) twinkle-1.10.1/cmake/FindLibMagic.cmake000066400000000000000000000040471277565361200176240ustar00rootroot00000000000000# - Try to find libmagic header and library # # Usage of this module as follows: # # find_package(LibMagic) # # Variables used by this module, they can change the default behaviour and need # to be set before calling find_package: # # LibMagic_ROOT_DIR Set this variable to the root installation of # libmagic if the module has problems finding the # proper installation path. # # Variables defined by this module: # # LIBMAGIC_FOUND System has libmagic, magic.h, and file # LibMagic_FILE_EXE Path to the 'file' command # LibMagic_VERSION Version of libmagic # LibMagic_LIBRARY The libmagic library # LibMagic_INCLUDE_DIR The location of magic.h find_path(LibMagic_ROOT_DIR NAMES include/magic.h ) if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") # the static version of the library is preferred on OS X for the # purposes of making packages (libmagic doesn't ship w/ OS X) set(libmagic_names libmagic.a magic) else () set(libmagic_names magic) endif () find_file(LibMagic_FILE_EXE NAMES file HINTS ${LibMagic_ROOT_DIR}/bin ) find_library(LibMagic_LIBRARY NAMES ${libmagic_names} HINTS ${LibMagic_ROOT_DIR}/lib ) find_path(LibMagic_INCLUDE_DIR NAMES magic.h HINTS ${LibMagic_ROOT_DIR}/include ) if (LibMagic_FILE_EXE) execute_process(COMMAND "${LibMagic_FILE_EXE}" --version ERROR_VARIABLE LibMagic_VERSION OUTPUT_VARIABLE LibMagic_VERSION) string(REGEX REPLACE "^file-([0-9.]+).*$" "\\1" LibMagic_VERSION "${LibMagic_VERSION}") message(STATUS "libmagic version: ${LibMagic_VERSION}") else () set(LibMagic_VERSION NOTFOUND) endif () include(FindPackageHandleStandardArgs) find_package_handle_standard_args(LibMagic DEFAULT_MSG LibMagic_LIBRARY LibMagic_INCLUDE_DIR LibMagic_FILE_EXE LibMagic_VERSION ) mark_as_advanced( LibMagic_ROOT_DIR LibMagic_FILE_EXE LibMagic_VERSION LibMagic_LIBRARY LibMagic_INCLUDE_DIR ) twinkle-1.10.1/cmake/FindLibSndfile.cmake000066400000000000000000000011321277565361200201600ustar00rootroot00000000000000FIND_PATH(LIBSNDFILE_INCLUDE_DIR sndfile.h) SET(LIBSNDFILE_NAMES ${LIBSNDFILE_NAMES} sndfile) FIND_LIBRARY(LIBSNDFILE_LIBRARY NAMES ${LIBSNDFILE_NAMES} PATH) IF(LIBSNDFILE_INCLUDE_DIR AND LIBSNDFILE_LIBRARY) SET(LIBSNDFILE_FOUND TRUE) ENDIF(LIBSNDFILE_INCLUDE_DIR AND LIBSNDFILE_LIBRARY) IF(LIBSNDFILE_FOUND) IF(NOT LibSndfile_FIND_QUIETLY) MESSAGE(STATUS "Found LibSndfile: ${LIBSNDFILE_LIBRARY}") ENDIF (NOT LibSndfile_FIND_QUIETLY) ELSE(LIBSNDFILE_FOUND) IF(LibSndfile_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could not find sndfile") ENDIF(LibSndfile_FIND_REQUIRED) ENDIF (LIBSNDFILE_FOUND) twinkle-1.10.1/cmake/FindReadline.cmake000066400000000000000000000027441277565361200177020ustar00rootroot00000000000000# - Try to find readline include dirs and libraries # # Usage of this module as follows: # # find_package(Readline) # # Variables used by this module, they can change the default behaviour and need # to be set before calling find_package: # # Readline_ROOT_DIR Set this variable to the root installation of # readline if the module has problems finding the # proper installation path. # # Variables defined by this module: # # READLINE_FOUND System has readline, include and lib dirs found # Readline_INCLUDE_DIR The readline include directories. # Readline_LIBRARY The readline library. find_path(Readline_ROOT_DIR NAMES include/readline/readline.h ) find_path(Readline_INCLUDE_DIR NAMES readline/readline.h HINTS ${Readline_ROOT_DIR}/include ) find_library(Readline_LIBRARY NAMES readline HINTS ${Readline_ROOT_DIR}/lib ) if(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) set(READLINE_FOUND TRUE) else(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) FIND_LIBRARY(Readline_LIBRARY NAMES readline) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Readline DEFAULT_MSG Readline_INCLUDE_DIR Readline_LIBRARY ) MARK_AS_ADVANCED(Readline_INCLUDE_DIR Readline_LIBRARY) endif(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) mark_as_advanced( Readline_ROOT_DIR Readline_INCLUDE_DIR Readline_LIBRARY ) twinkle-1.10.1/cmake/FindSpeex.cmake000066400000000000000000000013041277565361200172320ustar00rootroot00000000000000FIND_PATH(SPEEX_INCLUDE_DIR speex/speex.h) FIND_LIBRARY(SPEEX_LIBRARY NAMES speex) FIND_LIBRARY(SPEEXDSP_LIBRARY NAMES speexdsp) IF(SPEEX_INCLUDE_DIR AND SPEEX_LIBRARY AND SPEEXDSP_LIBRARY) SET(SPEEX_FOUND TRUE) SET(SPEEX_LIBRARIES ${SPEEX_LIBRARY} ${SPEEXDSP_LIBRARY}) ENDIF(SPEEX_INCLUDE_DIR AND SPEEX_LIBRARY AND SPEEXDSP_LIBRARY) IF(SPEEX_FOUND) IF (NOT Speex_FIND_QUIETLY) MESSAGE(STATUS "Found speex includes: ${SPEEX_INCLUDE_DIR}/speex/speex.h") MESSAGE(STATUS "Found speex library: ${SPEEX_LIBRARIES}") ENDIF (NOT Speex_FIND_QUIETLY) ELSE(SPEEX_FOUND) IF (Speex_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could NOT find speex development files") ENDIF (Speex_FIND_REQUIRED) ENDIF(SPEEX_FOUND) twinkle-1.10.1/cmake/FindUcommon.cmake000066400000000000000000000014031277565361200175630ustar00rootroot00000000000000FIND_PATH(UCOMMON_INCLUDE_DIR ucommon/ucommon.h) FIND_LIBRARY(UCOMMON_LIBRARIES NAMES ucommon) FIND_LIBRARY(USECURE_LIBRARIES NAMES usecure) IF(UCOMMON_INCLUDE_DIR AND UCOMMON_LIBRARIES AND USECURE_LIBRARIES) SET(UCOMMON_FOUND TRUE) SET(UCOMMON_LIBRARIES ${UCOMMON_LIBRARIES} ${USECURE_LIBRARIES}) ENDIF(UCOMMON_INCLUDE_DIR AND UCOMMON_LIBRARIES AND USECURE_LIBRARIES) IF(UCOMMON_FOUND) IF (NOT Ucommon_FIND_QUIETLY) MESSAGE(STATUS "Found ucommon includes: ${UCOMMON_INCLUDE_DIR}/ucommon/ucommon.h") MESSAGE(STATUS "Found ucommon library: ${UCOMMON_LIBRARIES}") ENDIF (NOT Ucommon_FIND_QUIETLY) ELSE(UCOMMON_FOUND) IF (Ucommon_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could NOT find ucommon development files") ENDIF (Ucommon_FIND_REQUIRED) ENDIF(UCOMMON_FOUND) twinkle-1.10.1/cmake/FindZrtpcpp.cmake000066400000000000000000000013211277565361200176070ustar00rootroot00000000000000FIND_PATH(ZRTPCPP_INCLUDE_DIR libzrtpcpp/zrtpccrtp.h) FIND_LIBRARY(ZRTPCPP_LIBRARIES NAMES zrtpcpp) IF(ZRTPCPP_INCLUDE_DIR AND ZRTPCPP_LIBRARIES) SET(ZRTPCPP_FOUND TRUE) ENDIF(ZRTPCPP_INCLUDE_DIR AND ZRTPCPP_LIBRARIES) IF(ZRTPCPP_FOUND) IF (NOT Zrtpcpp_FIND_QUIETLY) MESSAGE(STATUS "Found libzrtpcpp includes: ${ZRTPCPP_INCLUDE_DIR}/libzrtpcpp/zrtpccrtp.h") MESSAGE(STATUS "Found libzrtpcpp library: ${ZRTPCPP_LIBRARIES}") SET(ZRTPCPP_INCLUDE_DIR ${ZRTPCPP_INCLUDE_DIR} "${ZRTPCPP_INCLUDE_DIR}/libzrtpcpp") ENDIF (NOT Zrtpcpp_FIND_QUIETLY) ELSE(ZRTPCPP_FOUND) IF (Zrtpcpp_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could NOT find libzrtpcpp development files") ENDIF (Zrtpcpp_FIND_REQUIRED) ENDIF(ZRTPCPP_FOUND) twinkle-1.10.1/data/000077500000000000000000000000001277565361200141765ustar00rootroot00000000000000twinkle-1.10.1/data/providers.csv000066400000000000000000000003131277565361200167250ustar00rootroot00000000000000# provider;domain;sip proxy;stun server sipgate.at;sipgate.at;;sipgate.at sipgate.co.uk;sipgate.co.uk;;sipgate.co.uk sipgate.de;sipgate.de;;sipgate.de SIPphone!;proxy01.sipphone.com;;stun01.sipphone.com twinkle-1.10.1/data/ringback.wav000066400000000000000000000407561277565361200165110ustar00rootroot00000000000000RIFFAWAVEfmt @@dataA}y~~~ʲs?=d|ԶJ -Utumw~{fM71AXrᴄT6$%548@DDKROA+"/?BAHF3(.;TktykaI&   )5AC?BOfµټ~o]MNZguyjaP7,05AQWXcfXRPM]t~{z}Ϻq_Zk}t_G1,=MX\]UGDEJa~zxȾ¬rb[boy|unql\NDEP_o{zsrpsurx|}kbgix~j__er}y|}k``kyqgfm{sms}uopt}xsy~~l`Z\gv|{~~~}wmq~~y~~~yvuwzz|{yx|~{|toosvobeotuplq~~yryxnjkrwz|}zngjy}rsyyrp|wpw~{d\_o}x|nntux~{|oqz}{m_RWepxtsx|tmrwx~|fVVY^jqv~}{}~}|~{wuv~{w{}shfgl{x|}z{}wsopyuks}|}zw~}ut|ywy~|spv~~xsy|{~}{unnrrlowvxyv|}y~upuz}~~|z{zpYPUNBGMKTk֭iD 5I]z軟iF,*./,%#"(;K]wwngfgopcbddfjha_[URME>BQ]imibXRXgyý¼tfdgjh]WY\[VQQM>03ALSUSG?K_q~}}Ǻ¹s`^dlmfcflhYPMKLOWdmniaZ_n{}}qvtr}|pgfkrx|wwqns~|~z{yvsv~vmdcgilt|~}yumeait|{xww{~~ysr{~}{~zw{~{wumddn{~zxrot~~yyyx{zwzyz~ztruyywyx{|wrt{xpy|x{xqx{y||vqpx~|~zy|z}~zwxmen~zo}~wvwy||}}}xv|xs}zw|}}|vrsy}|uv{|z}rnq|xomr}xv|~x~}yzjYYn|vnip~zxqup_[hzywy~}z|x~}|~{mhn{uyyuz}uv{vfagy~}yojd_gv{}ohs|~~~~~~mn{z}sj[e}x{~}w~}snv}|}xx{uz~|vrqz|oijpuyzvru||y|}zx{qjq~yyru}~|~xszvgkxmfnx}{~}{slor|ztjhilx{rljny}{}oic_l|}tnlpx~vnlmv{tljms{~|tonv~~vmnsx~~{tnmv~}{xw~|~|zxzy{~~zz{~~zxz~yvw{{}|xtv|~|~~~|~}pqux~|zyqpupo{}vx}|qpuz~yxv~}vtx~|uy{utx|xw~{}~}|vonoqu}~{tjlqz|}{y{~xv|przoacmx|z{vy}~|~}~}v~xvssx|xzvqyvsz}tkv}zxxtqmfgn||wyyuzxwusz~~zqix{sjp{}}wuyzqr{vns|y}|rhn~vw}o_f{{sw{}vlp}{routfdnyoq{wq|÷|k`dq~vw~|z~kbhwtpv~rkpsgpsjhmvxilz~~{vtx|ghvysv~~xy~|z{wnqz~~|z|zvw{xrh\ST]ciu~~~|wv{~zy~~~~}|yz||~~}~yv~yz{w{~vnr~~~{yqeiu||x~}~{y~~|~yz}zzyx~}~{vuy|vxvz}qu|touwrx{xzzt|z{|z}zymlv~{vmr|{}y{z}~yz{uz}{zspw~~}vvxy~xvx~{z{x|y|uqv~|z}|}}z||~~y|~wz|zz~{xvx{vy{wy}}~{xx{}tosw}~~x{~|xutvz|ssz~~{x{~~wtw||~~{uvy~{|~~{|}}~wqlpy{{xv|{usnnsz|trw~zx{whlu}wvuz~|wps~x~obagptzw}|stz|z}vpvxntwkaiy}wx}ukpzsho}}{|}{~~zvxomuxqxyruz}|~~~{z}zu|yw|wmis}~|xttv}||~wsr{|wtsz~~~zwxysz}}|x~|uu~}|~{zysz|~{|}|~|y}yrkmt}~~wy|{|{uw}}rmiksx~~~{|zzzkjuvw}|}}|}~}z|xuwy|{vtutt|}{y|~}~|z|vojp|||{y||{yoeiu|~{~wsmdfns}~zxzvrw}xwvyywusuxy~vonpu|~}~yywrsvz~{||z}{tojlppz~}sqtpvytt~{|zuxyy|xs{~{zvy|yx{~z|{shbfmz{w{~{yz~zkjt|~|}spx{~xkkxxuqw~{zy|~yw{~}mdlxqggouxyuwzmisznhjw|z|~kfiumdcltwy}zgdp|~~vomrwydRWjw|sgirutzofksuzpcfknxpcdow}whfikr|mjmsztjggrsgdclxztppw{wsihqynlt}{ypiq}oiort}us|zrv{}}xtv}uprty~~ulo}~zrrx~|wvw~}|~}v{zvz}{~x|uquyxy~}|yxwv}~zrqzz}ykcixqjv}|ndjv~}~{tt|y{~wjacjuvnlq{}{~~qimu}{tu{|}~wmlru{ztrru|tqpryywy|zsw{xy{{~}zy}vkjqw|}yx}xtvzyxy~}vvz~}~|}xqpx~}z|~{}~zop{~}{}||~|{ronmqtu|xz~}{su~}z{~xtyzw~{upq{}z~~{yz~~yssx}zv|}|}zvz~~{vx~ytw}xqt~}rnqy~~~{mlsy{|}tntyz|~~}qturyzwz|~z~~{{|zz{}xnefn|~plt~vqms|upv}{zsjq|qkr~~}xz|rght}wrw~yw}{{~yz|{|tu}zzyzz|~tqtwwtv~}vtvz{z|||{slnwzsnikw{{xuuy|yv}{x|}upu~{v{}{zzodbiry{vwy~solly~~ww|yuvz}yv{{{{|||~vuwy|~|x}{u}}wyyy~|{~~~vokqw~~ymeks}~}xt}|}{stx{}x}yrlpw~~{snqx~vvuvz|vsx~}uljpz{|}|}xqlgiry{y~uos{}}x{~zpovy|zywqy}vmip}y|}|}{yyy|{z}~{wzwsoquy|}~yxzxy}~|wz~~}~z|{~|sr{ww{zw}xvzvxwtrsz}ury~~xy|zx~xy{w}}{xy|yyvqrtuxzzsvyy|~{~~~|~~}}xxwy~vuywppx}~~~{rw|wy||}}||}~{~~~|~~}|yzyz~~xw}{w|~}~{}|wy~yx|{~~zstwx|~{wy~~|{{z}~~{z}}}z|}{|~}~ztow~}~~|{{~}xy|{y{~~~~~}}yrw~|}|}~y{~}zxz~~~~}}}}~}}~|z~|z{~}|{v{|y}~}~~|~{~~~z|~{|}~~}~~~~|}}{}~~~~~zx{}~~}{wvz~{x{}{}~{{~|wy}{{}}|}||}{|~~}~zz{{~|~~}}z{|}|yx}~vtwz}|zy|}~~yz}~}~{}|z}}y{}|~xty|{|xw{~}~}}|zz}}y}~{xx{~~{x~~yx|}~~}~{z|{xxz~{y|~~~}~~}{{~~|}|{~~{}~~||{wy~~}~~~xty~{}}~~}}{|~~|yz~|yy{}|}{y{~~}}~~~||~~~~|{~~}z|~~}~~~~}~~}}xy|~|}|xz~}~~{|}{~}{yx|~~}{}|yz{|~||}z{~~{utx~~~~}|~~}ywxy{~{z}}{{{~~~~{{~}||{zyy|}}|}~||{z}~~}~}z{zz|}zwy~~}zyy{~{{||}zz|zxy{|~}{z|{z{|||}|~|z|{wtwy|~{||}}|~~{{}~~|wvx{~|z{}|{|}~||~~~|{yz{}~~~~}~~~~{xy{{|~~|{|{|~~~~~|}|}~}}~}~~~}~~~}{z|~|}~}}~}~~}~~|||~~}}~}~||}~||}}|~~~~~~}}~~|~~~~~~~~~~|{~~}}~~~}~}{|}~}~~~~}~}{|~~}~~}~}~~{{}~~~~~~~~~~~~~~~~}{|}~~~~~~|{|}~~~~~~~~}|}~~~~}~}~~~}~~}~~~~~~~~~}~~~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~twinkle-1.10.1/data/ringtone.wav000066400000000000000000001271751277565361200165570ustar00rootroot00000000000000RIFFuWAVEfmt "V"VdataQ~}|{{zyxwwvvuutttssssssstttuuvwwxyyz{|}~~~}|{zyxxwvvuutttssssssstttuuvvwxxyz{|}}~~}|{zyyxwwvuutttssssssstttuuvvwwxyz{{|}~~~||{zyxxwvvuutttsssssstttuuuvwwxyzz{|}~~~}|{zyxxwvvuutttssssssstttuuvvwxxyz{|}}~~}|{{zyxwwvvuutttsssssttttuuvvwxxyz{||}~~}|{zzyxwwvuutttsssssssstttuuvvwxyyz{|}~~}|{zzyxwwvuutttssssssstttuuvwwxyyz{|}~~}|{zyxxwvuuttsssrrrrrrsssttuuvwwxyz{|}~~|{zzyxwvuutsssrrrrrrrrrssttuuvwxyz{|}~~}|{zyxwvuttsrqqpppqqrrstvwxy{|}~|{zzyxxwwwwwxxyz{|}~~}{zyxwvutsrrqqpppqqrstuwxyz|}~}{zxwutsrrrrrrstuvwyz{|}~~}}||{zyyxwvvuuttuuvwwxyz{||}~}{yxvutsrqqqrrstvwy{||}~~}{ywutsrrrsttutttuvwxxyz|~}{zzywutstvwxy{|~~}{{{|}~|xtqpppppopqrrqppqtwxwvwy|}|||yuqprvz{{zz{{zvsqruxzz{|~}xutvxxwtrsuwvtrsvz|zusty}yyzyvqnpu{|xwxxwsporvz{zyz|~zwvwyyxtqpruwvtrtvxwsonpu{~zwttwz~}yurrsuy{}}||}|yvttttttsrpooooonmkjijkmpsvy|}zxvwy{}}|ywtstuwxxvsqprv|}}~~zvuvx{~~}ytpmmpsx{}~~~}zxxyz{{{yxutrqpomkigggiknqsuvxyz|||{ywuuuwy{|||||~zurqsvz}}{xtqpruz}~|zyy{~zwvx{~{zz{}~~}{xusqonlkihhhjlpsvxyyyyzzyxvtsrrtvxz|}}}}~~|||}|zwsppruy||{xurqsv{~|{|~|yy{}{{{}~~}zxuronmmmmmmmoruxzzyxwvusrponnnnpruxz{{zz{}||}~}zvsrsvx{{zxurpqsx||xvvx{}~|{z|~|||||{ywspmkjklmmnopruwyzzyxvtrpoonnmmmorvy|}}}}}~zxxy|}}|zwuuwy}}zurqsw{~~{xussvy}}}}{||{yvtux|~zuqonmllnqtusqnnoppnllorttrru}|{|{xutvz}{wqnnopnkhhjlmllmquz|~|xutuxz|~wstx|{vqnqv{|zyz~zrlkmqssrruy{zuqoprsrqqtx{}}~~}}~}{xw{|xx|}yy|}{{}~}wpjgikmmlmptwxyz}{yyzzxwwy|~}ywwzzustvwvuw}~}zskfegijjjlnqssstwz||{zzzzyxx{zwx{~{{||~yx{|yvurmgb`adghhhknqssstvxz{{{{|}}~}~yvwyzxwx}|z{~|yyxvqkfeglprstvxzyxxy|~}}}{||xwy}}|xsrvz|{{~{zyvqjffimpqqqsvwwvuuvxyyyyz|~|yxy~~~|}zwy}}}~~yurpke`]_djmmllmoppnmnorsuvx{~~}}~}xuuvxxxvussstuwz||{ywwx|~{yy{~}zvsqooopqrqponmnprttttvx{~{wuvx|~~~}ywwxyzywtqppqtwyzzxvuvy}~|{{~}zwtstvyzywusstvwwwvuuuvwy{|{yurpooppponopsx~|zz{}}{|~{vrppprsttsqooquy|}|zyxy{~|{z{{{{ywvvwz}|{z{|}|zxvvwxyyyxvvuvvwyzzxvrpoprtttsstwz~~|xtnigfhknprrsstvx|{wtrsvxz{{||{{|~~~}{yxvspnlklmopppooooopqrrrqqrstuutrpnmlmoruwyz{~zuplkknruvuspooprstsqnlihjmquwwwvvx|~|zwtqooooonmkjjklmmnnnmmlmoqssrpnnprux{}~}}~~|xsnkklosvxwvtrqrtwy{zwspnnpsvxyyyyz|{wtstwyyyyz{zywuuuutqommmmkjjmquvusqqrsrqsx|yvutsqnmnqstspnlkifdcdgkmoqsvyzyyy{}~}}{z|~~|{}{upmnprrrrsuwvtqpqstspnmoprstuw{~}{{}~~~zvrppqpmjhilprstvz}~{yz}}zwtplihiklnnnoqrrrrstuusrqqx~}zyyxusqrtutrqsx~{yvqligedcaacgkoqrrstttstuxz{|{{|}}|{yxxvtpnmnprtvy|wsnh`ZWWY[[[]`ejmmmot{{uqnkhc]XTROMLKMQV[`ekrx~~~~~}{wttuxyyxwvvurnjhhjlnoqty~{uoifeddba`abcefilptwxz}ztplha[VSRQPMKLQXahmrw}{wuutsrrtvxyyxvvvwwxz}~|zwsokjknqsux|}vqnke_[Y[^`_\Z[_cfggkqz}yupjc]XVTRQQSVZ^bglqvy|~zyyyvqljnswxvtuy}}zyz~ztqrtwxwwy}{xxz~|wspoooonlie`]\^cgkpv~|wqh]RLMRUURPSY_a`]]dny~|}~}zvttvxz|~{xxz|}~~ztqsw|~znf`ZSLE?<<@GP[fntwxz|}||}}}{yy||qe[TOOQTY^bfjou{|yxy|uld^\\_cfikmqv}}wohcacgnt{zofa^[XTPMMPV^hrz~}}|zxvuvy}si_VOJIJOUZ_cgmt}yurqrtw|~ywx|~yvsnh`VMGDFLSY_dinu{~rg`\\\]]]^`djqyzwsokfcdhr}~zwsng`XRPRX`gmqsvy}zsnjijmpt{}wrle[RIC?@CJT^hnqrtw{{sjb]\^djorrppsy}yurpmkkmrz~xsniecba`^^_ciorttux|vmeaadjr{{wtndYQIDBCIS]eijkpuy{zwutrnkhhkmlklnsyyuuv|pfcdinprtsqsx~zyy|~~|rheeirwqkjhku{~}ohhjlquy}r`SSSQQOJIMPV^grzwqjcfrz|{qnnghu}ztvuwuwsrsqtz{yy{ut}{tvtlnqd[behw}snqmpz}zvuutx~|zzx{y|xtxue\]]ZUQNMNQTRKEHTdmonpw}yyytkirxzxj_ZWTTY_cdglnkc_bksvtsuzysqx~x~}wvupkkpt{wj`^ac_XRPONPVaku|}wuy~}sqwzrs||vvxi\XYWNFFQalpmjffiouz}{ytpry~zsnmjd]\_a^^eq{~vfZY_gkjgb`cktxurtz}xuttvwtpq|}{ywx}|zxxrhaadea^_bfggea]\cnx||~}{yskdekngZTW_`YU[hwyrprutpkiiihiovz{zzyyz|~~zwtrty}zvv~|}{rnqx}tmieba^XRR[honhdfimpu}yuuxukcadfc`bhljhjpux{{~|sg_cp}|tnnoonkgfkrxwrorx{xspot~~{~zzzuportsolmlf`_djlhdcehkosvy}rifeedddda`djjc\^k|~}yvpkhhiiiiknqty~}xsmjkpssokjiijq~}wsrpi]QMNPPU`ksttw|~zuuvvtrrvwromnvvrmaamsu{xos~zwtyzsmjpx~ykirtv|wlmtv||~wh]WU[dc^ZVU[`gov}z{umjcVV^aellju}~ysnmqzvlib[bpuy}yvyuuz}vkeaaeimt|}t{{pgbde]VSQRTSU]fmsrosx}~yytiilfa_WQX__emow|vuuy|~}qfaZYfrw{ww{yrlgbadgozus~umfcec[VTQRTTZeouvrqw}~{ytrsnilkc^[UYfklrvvvqoot{}ne_Z`msw}xutvwrkhfdfjnx~zuzxslhf_XVTSVWW\chlmkow|~{wxxrqpjfg`XXWW`jjlsvzwuttxz}z}zohb`jtx~{uqopojikkjjkr~zwtt|zvsnjd][\[Z[\`hnoomls{yvxunliddbYQSV\kvxz}}}wtqrw~~~tieabkpotzyxwrowxkehmsy{z}z~}{|zx{|}|{zzutsokjjjnqtrldacec_\]dlsxxvssuuqlkr|{|~}ywz~xspmjhkpuvsqsvtpkggknrvursx~}uljox|yrnmjfbadhmu}tpomga]\\[[\^`bglomjknqqppuy~yppuwvsqqstuzzxsja_dikkkotvvwusrty~{uonprolhb[RLJLLHFMZgosv~~ule^WQMPW]^]]`cfilmpt|ysrrojinrtssu{~wtsqppnhb_\YSJGMU[`cfkt||pf`]VLEGLQVY^fnsz~zsh^XURONRW[_dhijmrz~|{~~ukillifeglquy|}}~~yvuvtpida_^`fihikqy{y{ztkc]XTPMLKNU^hort{~}sg_XSTY_efb_beefhmsw{|||zwutvvplmsy~}{{~yy||zwtqmf_YSNPYaeeejsy|vngdaZROPRVY[`jrx~yle`\WTU]ccbcfikklnoqx}|~{wvvusposz~~xuvy~~yxy{{{z{}zuwxqkf`ckmnv~vng``fhffebejjlqrsvslknt~xqiggbXPIFJSY]cfglrsx~{z|~yxxsrwy|~{{wxwvsjcfls~zvtpnprw~~{uturpmd^ahq}zwvw|yxzzxske_VVZZZXPKPV_kqu}wpnqxxvwtpkcdp|{rh][aflstuyxsqruz}{unlov|ohikq|}wsrpnmhdgozuole[VRS\dfhiegmorussvsnnnq{~}vqpu}}vqlgmyqi`XZ^`gnrvwqmnrx|ztplosu{xwxuy}vttrqmd`dm{zqbUPMR\`aeggmqqv{yvqf`aclttuy}uooszyupnu}zog_]achryzztnpw~|upmprt|z|{|zx|}zurz{slc_gr}~sgZW[]adbcjpu|~}~xme^Z^bdffdjs|~trvsswy~}ywphdachjmqu|}uqstsxz{~xnjmjjrsrz~yvsonomlpz|ypbWMKVdpyyu{{rpeaovtyuefqszusz~~{m^[ZW\gn{{~x{eUWUMUchr}{z~xrv{{rbZbnzvqsoqxsp}x}rngYW_bj|yttwwmn|o]SGISNFOQMYkmpxrikh^f{ob]ajqwm_[XROZp}~vfTFDMX`goy~~zsot{ocR?45?GMV`d\MEHLNRb{l[SV^dhlqsrpongZOP^oz~qeXPT`ku}gXW[[Zbtwrrtyth[PNVbhjmqsrpqsphbgwŸoZH949EQYakrn^PNW`djw²qd\\`ekpqoke_XQJFHUj}sbZUQWizva[_b_^luty~of_\\`juz{~vjbaghixoXID;36HY_]_im_PNV]]^oui`]`ksrpssiXIGKJIWo}haa]Zbsr`Y]a]\lyqoolp|une[Was{xvy{tf_eiej}ŴlXQK@FYs|ml~c_jjn{omlgf^lxYJYj|wj_ajekwtqzk\JHEEdw]BD]xssvxaXXe{~jXHEXsw]W^^clzwmQJMVu}cPN`rsygZ]_q{lUKbzxbZ\]ir{}kULHJk}_JJ]vus|lXWZq}vaOIYmnYVZYfx{mVDDLn~gYUg~xx~q\XTd~~mUK^owf\[ZfvwbUGCHh{[KK[pu]XWh|x]LFUiqb`]S]t~yh[H@Gdåhhwqhq}z{kp{TF[oyjbio\he]ds|u^V[pz]GIarfn}|fau~rt~~ox|cOH\w~{teeox`]tpRQf{t\[lrvhXMOsohtu|nwjlpwuiS:@]xs|zjgm}ZBJja[XafqnNKOqZ]skkxpwtfwwwaE@Gwt{xkm{zqT[vxsmůn_pc;2JurtuUQgy_YlrRUruq{vY^zkIHc{ijwu{ukurQ9D_ushU`yva[nwb[jqh|kdo~zzVIPpwegylp}mngOBNcyxfsvg]n|_jojk~doiXT^uoi}kijhcp~zǦyow~cOMOl}wVXnoedi_Sgrqxzm|me^_}iirkfitOKg}ls|rk^TM\~ufkurgqqoxtrzudeha[_ug]g|^YP^vwluxvj`T\swpWTa{`_Zfljtmprwltnrqgao_VNcyfbnkprogmz^ZShzX_slrkpozgijhdpuY_bsXYhwZ\[xxbovyuw`ZYpuydNPjm_gpr^_w~mkxw|zoq{h[]uo_^c}~|bTa~mfojkj^_o}|aYczprulopef|peja\]ryRIRyg[ZlkiuwptŞ{at{p_jtkdD@Gu{bK\vtqonQY|le|wlWJ]²TD\nlrh_lbHYnisSOgvgbSNj˸eZz{nxdHTnslPOm~i]KD^wRLm{}pnq[mbCFanXI::[hQ]gixbTL_ySHM[y]@DNLjv^PMes|}ysp]=8CZ|Ƚ{ckxu}cQKWv}}{~oJ<@OhcJLSTprffsryeA34D^yhrzv|bMJRy|rhu}}q]YbupUUUTd~kUGIXt~tO>>QjqzdOJNo{ywk\iosfSQ\nqUTQUinVPVd|vP>CSkqvv{u\WYf|vcLPR[ptYY\mkTTMN`q^cl}}egprhPVXhoqrvx_^al}v`FHV\uu^calnSQQShr_ltd`kngS_[cyuuwy_ffk~|fKDUZqw`khnvZNMI[m_q|i\jhwnY]`nzpoq}ghjn|{s\JDNXqfefnrRFDDUtwir|mahj|r\\anykml{}ighky}vcODOWo~gdiquRA?>Tr}tym_chzggu||hikx{wrysznWD>^}qv|{gE1/Het]Skz||~|~s[\sztlehx|pW?:^xyfE*.Lm{oVRm{xv}zys~~cKVpqxs`dpxTEZzotmVRg}xd?:Lrjbn|e[mgbmh_gcevulzj`i~nPJ^op~cX[sw]?DX}ch~h`ssVS`}fcr|edrzbdwlulYXmuc_rwb]nfQPeqcksgSTmeXXunp|njtbdujz_TQseSWyeYXq{`PPwoerñzfSZ}]RRuk]dk_cb\emi__nzde{XN_uhd{]UnpnqZRd{\OXvoZUlvfawybkoUPoiWahOGfwc\msbX}zt`Zvy^Le}gROnrisZ[|sjzkX[oiyjYVzr_cjZ\|z{^NZ}mYTmtcRayt{zimuxqYNWr]`rzfNKVy~fZkn`néy^MdjY[p|fVK]g`v}dVdvlUOkygsqVSm{c^teQazn^ROj]NRc{h\e~c\k~~sdQUrfX[qo\Z_trfhruch{ͻuZS_wgZWoqdbkxlnxmY]nsw{kUUc|zljvkiqogkwsdfywuv^DDVqz`WZqpikx|v|lqŻ|tssbU\dlzzvyh]et~|zrt~uqn`iu}xuve[eq{}xxxrsprbV^iwxmvzwyrn~zuseX`grz}w~wppncZcn|wseihqvy{|vnwwtnd\gpw~}z{{~rb[aiv~}w{xqo}{~yngoou~}~}||pgcjpsxtry~~~z{ppfX[djv~~~uw~uszzy}vy{prsspke_kxx|}~|q`djcgims}||~|yyrptx}rz{spigfqwu{}}~uma_dY[]fu~zxy{uw}unrvpqjfhnux~}yhb^a\^acv~~|ysmr}}vwy}twzwlijelsv}~}{yl\__YUV_fw~ztlmv|{y~ypssnjjkknuruunumm}q\WZTRTVhyv{uvpnl^hx{gl}{xuqg`o}jeirtxxn{kn{aXOXPJHTj~}vztkaj}qht{ukozb\j{okkq{|~ro{von~{eWWZPJDVozyocbjmir{z|pfdoqk|z{|}{zz|~z~tliggfZTOVizz}{nnopwuvs~tit~wks~~yy{{y|z~ujnkoke^[cizz}uligkhlqu~w}|v{yvwuvuvx}qmnuxtpfgjnxust{yknmptw}xvrwxsomu~wrqvuwy}vpppwuqlhhmpssmpxwlnruy{|zxvtqsnefjy{spw{{y{zsrtvwopx{zy}zztu{ymlqmqqu|{~}vqt}{~~}|u}}qeinpqjmsz}xnsxyyty|qu{}yx}w{vjdnv~xqw}v{}xpusgow~xtu{ty{rqroqx~uns|xw}zssymnuxy~|~}vorrpoy|wutxzyxqrsst}}yt{}wnqumot|twzwx}vlr~uuw~zwz||{ndn}z{xz|}{kef~vst{xtrsop{vmy~lbe~vjkoz~{vxwrt}d_m{~~{{~sgs|u{}}{vzrem|khy|y}nalqltx{zzv}}ifu|uskhpxwrg^m||{pbixxnp~}shtsnuwtyuxxomoyviZahp}~~zonxwiq{{h]^cp{qbiv~}okt~xjakv|nknx|qg]coxyn_Yct|qt}l[Ybqxpjtvf`ft{negq{}ma_iv}vpssc]cr|pjjsueclvxmkp}rd_evropxob`jx|vx{o[UWgzwiffo|pebly}wz~tffk{smmt~~miku{liinvwgdeo}wljio}mjlx}njkpwttt|~rmlpw~hegtwoopu~jeekx|vkfinxkfhr~zmfjmu|wx~wnpx}|hdgpzslnptlcfkw{wohksxkdkqwvnkpsw|zpmtxlgmt{xnlnn~}k`cjt}wmefszogkrxumlot|z}~piirxrmrz}re`mnchqvzyv{}tihjtmcelnsxv{vjl|}{pghttmsxx{q_]lqbbky{{tv|~m^cxnjozzsko|wjn|~}~l[_qwv~ysyvfivkdgrzwpiow{laj}zy~zohnzqejz|~maj{}~wu~yx~vjbhwzkcgsz{rnox|}ochv{qp|yvy|tjht{x|rw{y~j][j~snuyf\`n~yjdm|unswoql`bpysr{zor}whah{|}weX\k}|u{~nejx~w|ws`W`pxqzsjs~shfs~k[Ubr{vtymitywzw{gYVfw~uoz}qkhx{u|z}rkvvniwxmgqz{qnr~vzrp~vkhzz}~wylgl|pn|urwrffv}oil}{on}|st~{wwps|mcfysrzsrwkhq{mo{{zrq~|pnutnrwkny}suzoowzkhqsw~w|vhkwxsz}typnvsfhttr|qgp{wvkp{wijxvktyu}l_hyys{risxsz~~|ywym[Zp~|phnzmhtwv|l[\r|njxrny~sjmxzwsm`bvtjbq{omx}tpz~ws}l^]owmixzxryrher{e[^oznixymily{tnrv}oecltprx~ztvshdjt}{rdacn}vw|volq|yqlhpz~ugado~yorx}yle]gv}qb\_jystzupsw~~shhen|}pcckw}rqv~trv{vkkhq}pcco~|wnlqx}rou|yklmt|p`]j{ytrxrkr}yhgkrsb^l~umhglsyyniq|viipx|ocaq}{{~~skitvnkkpzyngerzqmkmu~{rnmyytsw~|qnkw}zzx~rliszsopt}sonywpmjqz{ojgr~wpqtz}omkx||{wplt~zqjkozxhdgttkjiq{zmknw~woqx|mlqvvz{olox|uohks~wkhmz~qffir}~xojnx~wpqwzokp}usxtlp}xrrxwutzzmglz{yxrkimywkekz~||yx}|nipzx}tlr~|vvvwslkr{k^ew}}}wnfgl}xhal~}|~~{ss}}l`h}yqs{meo}zxvqko}xe[bt{xtlb^dp{igu~{vqtrfguxt{}qpzru~}qkqrfhxulkxub^f|tmp}txvosmek~|w{rmuxoqsmtzu|~cY`vxtvs\Xby}mjqws{wtzw`Zd|nlwunrpozzooxs^Ye|p__nsdbmwnrx|vcbqugl{rr~yputmthZ^q~h\dx{kbevrmvzw~odi|mbjqgkyvmmyj_ctx`Vbv{pnulem|zuxqgm~{f_j}khq}~sqvwfcm|}q_\hxtquuigq~xy|kissegt}}wiiuss|qcbo}|l`ao}|rs~pfjwz}sfjy{~iagxx~ndhw{qwkdix|jai{}tv{kcl|upw~i`i{|wfcn~~y{|nkt~rknqvxym`_m|{ttxxkl{torw||{}i`ds|rq~}occtztuvxww{kdn~wquofk~vorttssvrbaozvy}ngk~vqwz{zztcao{}|spu~piprmtyzyw{xg_dr~vrrw||qjhp||siflxzibgstnowvoltzqf_blsd^fsvonvvnlt}xnffo{kektwlfiuxoluwmeel}zkfmwujdgt|rq{wmb_guobamxysuvkir{xpibclzuhckt~umimy{yvlb`gsna]gpvswymkv~~vldfp|xidms}|rkksvhfqzyjekr{oggqwxvqc_ekr~uheny{qoux}vihu~rhiszqjnwyk`_hqx|ppzqb`hqx}oms}~rmrx}rfemy}obakuolr}tihnt{wxrdaelttecmz{ohjourdgqwmkownjr|ujdfiq~}rt~{rhdfjuxihqxommt~tffp~wkkoy}|ngo~rrsx|wtw}zqoouxsru{jfoyyxzxqln{zhfp|stw}pkvvux|}zu{sr{|pmquuqnwtdbpyxyyxtnutccpzz~|}}kju}{z{zxvu~os~~xwwsplkxsghv|{{toklw|iamristpritofrrn{tm{nemwd_l|ifrpjtzgdovdcrrmwrrnlzmgr{mn{xzg`h{~qfjxxjl{}nmxrdftsimvfdq{u{{~rnxmchwupxyjlzunvukqqeiy|jbj}~oiqlah{tr}yvlqze_j~pp~pfn~nkwnit~keoyd_kwikx}f^iuzvo{on~kbmsioldn|sv~hcpthizzfbnogo~ganyqylk{{qv~po|ykgtwdbp~ttqcg|ogoqdhytdcvr`aszpqpfotq}tjoudgzm^bsvjpjaj|jgxngpscg|nagzxnsjdp~mn~okuqaf}mbj|tkt|gcrzij}yiiwi\ezfam~ojwoiq}yqttgl~}rntzwpfhyyigqxu}ojsngozqrz~zpgkyiivyxojs~mfpwnr{{oinxko|zyojv|kgttkpy}xkejpekzyx}nkx~okxtlv}mhnqfn{{vxtggw{ll}|plvyljnunkpu|sihqyxy|zz}~}vmmsvqnrv}rjku||||xux|{qghq|tqortyqikwyvztihryvtsprz~nho}}}}ztrwvigrzvrpmmuxifo~yuzteak}}wtsrps}tow{tmls~~qeer{vqmkmxwjiqzzodcn~|wsnjhn|}rpy{tor{qjn{}rhbeo~souvmjsqhhs~}shdk{~{tg[Ydu}oko{wjdju|qpu~vljt{rnu~xnc`fssh__iwytt}uhdhp||ustywohlxzsrv}zoffn||oe^bnz~trtz|na`gq|~ywy{~}rkq~zyz~wh^`lz|ob\cq}|~na`hs}}wtxwsxpgjz~{}vwoaaowf\_m{zzm^Zeqxyvv{zysmq~~{|}mcgv}y~~m_\bo{|y~yh]amw{yxzyvz|rqzvv}uifp}yz{k``js|~}}~qa\hz{kbh~pn||op}po~qqxjhxw|smx|i]_p}phlzzqcam|vhbjttzrvtko}rutkn~|z}mjvuifpwieoz}lcgt}|odetphm~uusr}{zkhqxoqsmsvkmysgiy|zsdbl{{kchz|mku}su{os|{ughuwpurpzqhm|ogl||pdcp}~widj}xklvvs{tu}x{yoq~sov}qox~wlktsjm}{vzqimxshgr{rvzop{rlsyyxryznn|tmq}shiwoirwv}jdn}qfixvoxtik|qkvxzqlxwlnplu{kbgz}ljwzqv{khvsjovsqhn}olwyt{ykl|umq~rsyhcm}oo|yt}qehy}mgoyqwmciwpu||ughz~ss}ztxta[gqow}wxj`fxwpuyv~xfcprqx{vykci|uoutr|qcbqvw|wznfmsouvuscaozqsz~{uqxnhqws{|strddt}vz{spyj^aozy{zz|yjdl~wolkqyj`es|ywtsu~redp|vrps{~jagt~zutu~sfepxrjfirrlr}~|vqqvtggs}wniir~mglw~wrqwviit~vlghs~mhoz}ypkjrshiuyojlw|njp{{qmmvpght|sieixyljs}yolnyqko{~tieixvkjqzyolp|sns}xjcftytwyl`]duups|{lfjw|xz}ob^dr~soqy~pjmy{vw~~vk_]dt}xywicfr|vv{{l`]dszssy|njo{zsrty~{naXXcs~xx|vifmzwh]^gvvqqt|thfn{}xwvy|re^am|~m`^eq||zyzqijtzvttz~qhiu|usyqc[an|qddmytknw}~~ytrwzldgr~xtv~qc]ds}mehr|ykfm{~~xsr{uhely~wv|zh\\gxsgfmy~qfk|wlq~xpvkcjyrjryquuc\evwr|~phn|{nkv}mgp~okvspz}jdm}{omxuqyr`^j|~rq~}|~kfpvkn|{kiuzmlx|qtufertkn{suj]_owpt}vijxslr}tilztlq~xsznejypkt|u{|hagv{pnx|skq~|omyrls|pmwtr}ojp}wllzwrypbboxvuwvqwvmp}|zmn|zyqgkzvigp{zpkrx{pdhw|uyuox}qqzzpluyvnt|~yolvndht~xonx~zxhbk{xtz~uvzqt|wmny|{~pny}ztjly{lho{|uwxz|onv~vv|vpvsqvyru~xqv}suzu|{mms{|vnio{|plsqlr{us|vps|{||rnv|pow|sqzzjglu{|yvy}ts|~srzxqsros{}{wuw}|sllvyqs|yz}rpu{~|z{~wqr{zstz||~wsv}ww{}z{~unq{}y|zuuyymgiox|uppvvru|zvw|{ojlryxohhp}urv}|ww}ynjmu}zrmov}spsy}vsv}{rnpu|yokmvxuw}xuu|wmikrzzqnpyyvx|~wvw}yplnrx}sigjtywy|zwy{rnpu|vljmw~vtvy~{ssvwqorw}vljnyywy||trvwqnpt{}rjjq||yz||sqt|zwxz~zod_bmz{vux{squ|thbdmx|ursw~}vtw~~|}~|qfbgs{wwy~umlr{thbep}~ywy~{srw}ywxz~wmc`fr~xuv{tnovsgbfny}ywy~zplou~|xx{{qigmw~rhcflu}}{}yokpw|{}yoknu}vsw~vibep~~}ndfo|tjmw}xy{meis~~wrs|sfaivxjdit~|ojq{~yx~ujho{ztszodclypeemz}rr{zuv}{~pjmxzvx~vu{ylfju~xxqkoy~xw|wrv{sszyy{ols~yw|zst}ujflxytwznkr}|vw~vqwyst}|wywmnw~ww}~tpuqgfozzw|wmnw{wy~~~tt{}uqvyyuotzw{{tt{zoimu|}xtu~snr{zuu}}vyyss{~wxury{vv}yqqx~skkt~~z{}x{zuz}uqt~{~{{|{yokq}vu{{utzzuw}rmp{|{~xy}ww|~|vqry}}~{snpzyvyyuw~zy~voov}}}z}|xy|{xsqu~}xz~}z|~|vpos|{||{vv{~ztru}}{}zuu{}|~}yuv|yvy{{ytv~}~|vsu|zrpu{}{wwx|wopy|vqqx}trw{{|unpx}yuu{{sqv~zvwzsmr}~ztopyztu{zvx{zoms}vrt~}xy}{usx}ypow~wpmq{zw{|ww}|xwx}{rnot|}vpory}{|~|zzwomot|}uppu|}{|}~yxx~xpory|uoot|~~~zyy}rkknv{topu}~~}{{}unmqyyrnnsz}}~unnqxxrnou|}}}~~|zz|wqqu{zrmnv~{z{yqmnryxpmow~~~|z{zvuvz~wpmou}zyz|z|{ustw~~wrprwxtsuy}~zwwy~~{|wqnpu}~yvvz||}{urqu|~vpos|{vsuzyuvy{xx{wpnpw{xx|{{}xrnou|yrnnrx{xy~|tpqv~|||upopu{~yxz}vrsw|}ursy{vtux}wplmpu|}zz{{{|zvw~ztqu~{zwtssw|~|vpos}voou{{~~vsx|z{|slmv}{yuppu}}uoow~slnv|y{|vv|~{}zploy~{wsprxzqmpxzplq{|}}}{wwyzzxy}{yy|}|xvuwzzyxxz{z||zyz~}|~~~{xxyzzz}~zxy{{ywvwxyyxwx{~~|{|{zz~~~~~|zz{{zyz|zyz{zxvwyzzxvvy}~}|}|{yz~}~|~~}yyz}~|zz|z{|}zvuvyzywuux}}||}|zyz}}~zz{|}|zy|~}}~|zwtuxzyvtsw|~}|zww{}xuuwxxwvx|zwwyzyvsty}|||zxuv{}|}{|~xvvxzyxwyzxxyzwtru|}||{xvtw}{z}}~|{~}wvwz{zzz|yy||wtuz|yz}|wwxzzyxy~}z{vswxqpuz~}{}zvz{uuz~{wx~~usyvppv{}|z~zx~~xy}xy{sryvqrx}~||zyzvx}~yvyyqrzwsuz}}yzww}zz~z{xnio{xux}}y{~xzyz{y|vljr~vtxzzxux{vz{}}{zzzzz{}~}}}{zzz}~{{|~|zzyyz{}~|ywxz~}|}}{yxvuvwz}~}|}~}yxxz~|}~||{xwwy}}|||zwttw}~}~~|zywvvy|}||}|zyy||{}}zyyxxy{~|||{yxy|}}~{xwvttvy}~}{|}~}{yz~~{{|zyywxy}|{{{{xvvx}}||}zxwvwy}~||||{z{}~{z{|zxxyz}|zyz{{{|~}~{wtrqqtx|~|zz|~}zy{~~zxvwy|}{zxwxy{}}wsqrtx}|zyy{}|yz|{xxy||zxwxy{|~}{{|~zvuvx{}~|zyyz}|yxz}~|zz{}~}|{{{|}|zyyz}}|zyxxy{~~|{|}~}~}||}~}{ywvvxy{}|{zy{~~}~~~}}~|zyyz|~~}}{zyxxyzz{|~~}|{zzz|~}~~|{{{{zz|~{zyyxwwxxyyyyz~~}||||}~}urwzzzros|~{|~|}{sllq{zwzzrqw~|~yst{{y{uopx|{~yy}xoilt|vw|vprz|}xsvz|~tor{}~}z}xokox{x{~{{|~}}~}|zz}~|xutuxz{zyz}|zz|~~~~~~{z{}~zwttuwz{{|}~zyy{}}}}~}zxxy|~{wvvwwxz{~}zxwxyz{}~}{zzz{}~|yxwwxy|~}ywvwy{|~~~~~~{yz{}~~}|{yyz{}}zxwxz{~~|{{|~|zyz|}~}{z{}}zxxy|~~}{zyy{}}{z{|~~}|{{{|~}|{|}~}|{{||}~~||{{{|~}{{|~}xvx}{wy}|{|}~zz~~zx{{wy}{xy|~|~{{{ww{{x|~||~~|~|xz|ww|{x{|z|~~~|}~wtuz}yy||||||{|}||~~|yyz|~|zyz}~|}|yy{}~|zz|}{z{~{yz~}|{}~{yyz}}|{}}{|~~zxxz}}{yy{}|yz~|{}{yy}}ywwxz}~{z{}|}{wvvz~|zz|}{|~}zxz}}yxz}~{xwy{|z}}|}~}xutvz}}{zz|}{}~|{}~{{|}zxwx{~~{yy|}yxz}|yxz}}{{}~}~zwvx|}{{}}{{|~~||~{yyz}~||~~}{{{}~|}|zyz~~}~~||~|zz||yxxy|~||~~}~~|zy{~}zyxz|}||~|zyxz~~|zzz}~~|zz|~|yxx||{{}~zww{~~}~~zxy}~|yz~}~ywx}|{}}wuw|}}}yw{{y|~{ww{}z{~zvvy}~}~{wx{}}yy}}yvx}{{xz}wuw}zy~~{}xvz~}~|yx|zz~}|ww|{|zy}zvv{|xz}}zvv|~|~}}|xz|y|~yvx~|z||yz}wuy|z~~}vtw~{zzy}zy}~}~wvz{{{z}{uv{{{~~zuty}z|~zz}z{~{wx}}z|~{|~xvx~}~yvx~~yx{zx|}|~}||z~~yy}~}wvz~{{~yy}~yz~}z|}xx}~|}~{|~{|{xz~{xz~~|~|z{|z|~{z}zxz~|||{~}{}}z{~|{{~}{xy}|{||z|~}~zz~}}{y{}|~|{}|}}z{~~}yx{}|{{{}~~~|zz}~|{{|}~}~|zz}}{{{|~~|z{~||||~}}~{z{~~|{{{}~~}{z|}|||}||}~~}{yz}~zyy{~}}~~~~}{z{~|z{}|{}}zyz}~{z{}}}~~}{z{~}{{|~}}~}zyz}~}|}~~}~~~~~~}{{|~}|}~~}}~~|zz{~~|}~~~}}}~~|{{}~}}}~|zz|~~}}}|}}|}~~|}~~~||}~}}|||~~~~~||~~}~~|{}~~~~~}}~~~}|}~}}~|{|}~}~}}~~}}}~~}{{|~}||}~~||}~~~~||}~~}}}~~~}}}~~~~}|}~~}|}}~}|}~~~~~}|}~~}|}~}}}~~~~~||~}||~}|}~}~}|}~||}~|}~~~~}}~~}}}||~~~~}}~~~~}}~|}~~}~~~~}~~}~~}~}|}~~~~~}~~~~~}}~~~~}~~~~~}~~~~~~~~~~}~~~~~~~~~~~~~~~twinkle-1.10.1/data/twinkle.1000066400000000000000000000077541277565361200157520ustar00rootroot00000000000000.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.2. .TH TWINKLE "1" "January 2016" "Twinkle 1.10.0 - 15 July 2016" "User Commands" .SH NAME Twinkle \- Twinkle 1.10.0 .SH SYNOPSIS .B twinkle [\fI\,options\/\fR] .SH OPTIONS .TP \fB\-c\fR Run in command line interface (CLI) mode .TP \fB\-\-share\fR Set the share directory. .TP \fB\-f\fR Startup with a specific profile. You will not be requested to choose a profile at startup. The profiles that you created are the .cfg files in your .twinkle directory. You may specify multiple profiles separated by spaces. .TP \fB\-\-force\fR If a lock file is detected at startup, then override it and startup. .HP \fB\-\-sip\-port\fR .IP Port for SIP signalling. This port overrides the port from the system settings. .HP \fB\-\-rtp\-port\fR .IP Port for RTP. This port overrides the port from the system settings. .HP \fB\-\-call\fR
.IP Instruct Twinkle to call the address. When Twinkle is already running, this will instruct the running process to call the address. The address may be a full or partial SIP URI. A partial SIP URI will be completed with the information from the user profile. .IP A subject may be passed by appending '?subject=' to the address. .IP Examples: twinkle \fB\-\-call\fR 123456 twinkle \fB\-\-call\fR sip:example@example.com?subject=hello .HP \fB\-\-cmd\fR .IP Instruct Twinkle to execute the CLI command. You can run all commands from the command line interface mode. When Twinkle is already running, this will instruct the running process to execute the CLI command. .IP Examples: twinkle \fB\-\-cmd\fR answer twinkle \fB\-\-cmd\fR mute twinkle \fB\-\-cmd\fR 'transfer 12345' .TP \fB\-\-immediate\fR This option can be used in conjunction with \fB\-\-call\fR or \fB\-\-cmd\fR It indicates the the command or call is to be performed immediately without asking the user for any confirmation. .HP \fB\-\-set\-profile\fR .IP Make the active profile. When using this option in conjunction with \fB\-\-call\fR and \fB\-\-cmd\fR, then the profile is activated before executing \fB\-\-call\fR or \fB\-\-cmd\fR. .TP \fB\-\-show\fR Instruct a running instance of Twinkle to show the main window and take focus. .TP \fB\-\-hide\fR Instruct a running instance of Twinkle to hide in the system tray. If no system tray is used, then Twinkle will minimize. .HP \fB\-\-help\-cli\fR [cli command] .IP Without a cli command this option lists all available CLI commands. With a CLI command this option prints help on the CLI command. .TP \fB\-\-version\fR Get version information. .SH COPYRIGHT Copyright \(co 2005\-2015 Michel de Boer and contributors http://twinkle.dolezel.info .PP Built with support for: ALSA, Speex, iLBC, ZRTP .PP Contributions: * Werner Dittmann (ZRTP/SRTP) * Bogdan Harjoc (AKAv1\-MD5, Service\-Route) * Roman Imankulov (command line editing) * Ondrej Moris (codec preprocessing) * Rickard Petzall (ALSA) .PP This software contains the following software from 3rd parties: * GSM codec from Jutta Degener and Carsten Bormann, University of Berlin * G.711/G.726 codecs from Sun Microsystems (public domain) * iLBC implementation from RFC 3951 (www.ilbcfreeware.org) * Parts of the STUN project at http://sourceforge.net/projects/stun * Parts of libsrv at http://libsrv.sourceforge.net/ .PP For RTP the following dynamic libraries are linked: * GNU ccRTP \- http://www.gnu.org/software/ccrtp * GNU uCommon C++ \- http://www.gnutelephony.org/index.php/Category:Software .PP Twinkle comes with ABSOLUTELY NO WARRANTY. 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. .SH "SEE ALSO" The full documentation for .B Twinkle is maintained as a Texinfo manual. If the .B info and .B Twinkle programs are properly installed at your site, the command .IP .B info Twinkle .PP should give you access to the complete manual. twinkle-1.10.1/data/twinkle.svg000066400000000000000000000070361277565361200164020ustar00rootroot00000000000000 image/svg+xml twinkle-1.10.1/sip.protocol000066400000000000000000000002441277565361200156430ustar00rootroot00000000000000[Protocol] exec=twinkle --call %u protocol=sip input=none output=none helper=true listing= reading=false writing=false makedir=false deleting=false Icon=multimedia twinkle-1.10.1/src/000077500000000000000000000000001277565361200140545ustar00rootroot00000000000000twinkle-1.10.1/src/CMakeLists.txt000066400000000000000000000042131277565361200166140ustar00rootroot00000000000000project(libtwinkle) include_directories("${CMAKE_CURRENT_SOURCE_DIR}") add_subdirectory(audio) if (NOT WITH_GSM) add_subdirectory(audio/gsm/src) endif (NOT WITH_GSM) add_subdirectory(audits) add_subdirectory(im) add_subdirectory(mwi) add_subdirectory(parser) add_subdirectory(patterns) add_subdirectory(presence) add_subdirectory(sdp) add_subdirectory(sockets) add_subdirectory(stun) add_subdirectory(threads) add_subdirectory(utils) set(LIBTWINKLE_SRCS abstract_dialog.cpp address_book.cpp auth.cpp call_history.cpp call_script.cpp client_request.cpp cmd_socket.cpp dialog.cpp diamondcard.cpp epa.cpp events.cpp id_object.cpp line.cpp listener.cpp log.cpp phone.cpp phone_user.cpp prohibit_thread.cpp redirect.cpp sender.cpp service.cpp session.cpp sub_refer.cpp subscription.cpp subscription_dialog.cpp sys_settings.cpp timekeeper.cpp transaction.cpp transaction_layer.cpp transaction_mgr.cpp user.cpp userintf.cpp util.cpp ) add_library(libtwinkle OBJECT ${LIBTWINKLE_SRCS}) set(twinkle_OBJS $ $ $ $ $ $ $ $ $ $ $ $ $ ) if (NOT WITH_GSM) list(APPEND twinkle_OBJS $) endif (NOT WITH_GSM) add_executable(twinkle-console main.cpp ${twinkle_OBJS} ) set(twinkle_LIBS -lpthread -lresolv ${LibMagic_LIBRARY} ${LIBXML2_LIBRARIES} ${Readline_LIBRARY} ${ILBC_LIBRARIES} ${SPEEX_LIBRARIES} ${ZRTPCPP_LIBRARIES} ${CCRTP_LIBRARIES} ${COMMONCPP_LIBRARIES} ${UCOMMON_LIBRARIES} ${LIBSNDFILE_LIBRARY} ${ALSA_LIBRARY} ${G729_LIBRARY} ) if (WITH_GSM) list(APPEND twinkle_LIBS ${GSM_LIBRARY}) endif (WITH_GSM) if (WITH_QT5) add_subdirectory(gui) endif (WITH_QT5) target_link_libraries(twinkle-console ${twinkle_LIBS}) install(TARGETS twinkle-console DESTINATION bin) twinkle-1.10.1/src/abstract_dialog.cpp000066400000000000000000000216401277565361200177050ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include "abstract_dialog.h" #include "log.h" #include "phone.h" #include "phone_user.h" #include "util.h" #include "userintf.h" #include "audits/memman.h" extern string user_host; extern t_phone *phone; // Private void t_abstract_dialog::remove_client_request(t_client_request **cr) { if ((*cr)->dec_ref_count() == 0) { MEMMAN_DELETE(*cr); delete *cr; } *cr = NULL; } // Create a request within a dialog // RFC 3261 12.2.1.1 t_request *t_abstract_dialog::create_request(t_method m) { t_user *user_config = phone_user->get_user_profile(); t_request *r = new t_request(m); MEMMAN_NEW(r); // To header r->hdr_to.set_uri(remote_uri); r->hdr_to.set_display(remote_display); r->hdr_to.set_tag(remote_tag); // From header r->hdr_from.set_uri(local_uri); r->hdr_from.set_display(local_display); r->hdr_from.set_tag(local_tag); // Call-ID header r->hdr_call_id.set_call_id(call_id); // CSeq header r->hdr_cseq.set_method(m); r->hdr_cseq.set_seqnr(++local_seqnr); // Set Max-Forwards header r->hdr_max_forwards.set_max_forwards(MAX_FORWARDS); // User-Agent SET_HDR_USER_AGENT(r->hdr_user_agent); // RFC 3261 12.2.1.1 // Request URI and Route header r->set_route(remote_target_uri, route_set); // Caculate destination set. A DNS request can result in multiple // IP address. In failover scenario's the request must be sent to // the next IP address in the list. As the request will be copied // in various places, the destination set must be calculated now. // In previous version the DNS request was done by the transaction // manager. This is too late as the transaction manager gets a copy // of the request. The destination set should be set in the copy // kept by the dialog. r->calc_destinations(*user_config); // The Via header can only be created after the destinations // are calculated, because the destination deterimines which // local IP address should be used. // Via header unsigned long local_ip = r->get_local_ip(); t_via via(USER_HOST(user_config, h_ip2str(local_ip)), PUBLIC_SIP_PORT(user_config)); r->hdr_via.add_via(via); return r; } void t_abstract_dialog::create_route_set(t_response *r) { // Originally the check was this: // if (route_set.empty() && r->hdr_record_route.is_populated()) // This prevented the route set from being altered between a 18X response // and a 2XX response. This is allowed per RFC 3261 13.2.2.4 if (r->hdr_record_route.is_populated()) { route_set = r->hdr_record_route.route_list; route_set.reverse(); } else { route_set.clear(); } } void t_abstract_dialog::create_remote_target(t_response *r) { if (r->hdr_contact.is_populated()) { remote_target_uri = r->hdr_contact.contact_list.front().uri; remote_target_display = r->hdr_contact.contact_list.front().display; } } void t_abstract_dialog::resend_request(t_client_request *cr) { t_user *user_config = phone_user->get_user_profile(); t_request *req = cr->get_request(); // A new sequence number must be assigned req->hdr_cseq.set_seqnr(++local_seqnr); // Create a new via-header. Otherwise the // request will be seen as a retransmission unsigned long local_ip = req->get_local_ip(); req->hdr_via.via_list.clear(); t_via via(USER_HOST(user_config, h_ip2str(local_ip)), PUBLIC_SIP_PORT(user_config)); req->hdr_via.add_via(via); cr->renew(0); send_request(req, cr->get_tuid()); } bool t_abstract_dialog::resend_request_auth(t_client_request *cr, t_response *resp) { t_user *user_config = phone_user->get_user_profile(); t_request *req = cr->get_request(); // Add authorization header, increment CSeq and create new branch id if (phone->authorize(user_config, req, resp)) { resend_request(cr); return true; } return false; } bool t_abstract_dialog::redirect_request(t_client_request *cr, t_response *resp, t_contact_param &contact) { t_user *user_config = phone_user->get_user_profile(); // If the response is a 3XX response then add redirection contacts if (resp->get_class() == R_3XX && resp->hdr_contact.is_populated()) { cr->redirector.add_contacts( resp->hdr_contact.contact_list); } // Get next destination if (!cr->redirector.get_next_contact(contact)) { // There is no next destination return false; } t_request *req = cr->get_request(); // Ask user for permission to redirect if indicated by user config if (user_config->get_ask_user_to_redirect()) { if(!ui->cb_ask_user_to_redirect_request(user_config, contact.uri, contact.display, resp->hdr_cseq.method)) { // User did not permit to redirect return false; } } // Change the request URI to the new URI. // As the URI changes the destination set must be recalculated req->uri = contact.uri; req->calc_destinations(*user_config); resend_request(cr); return true; } bool t_abstract_dialog::failover_request(t_client_request *cr) { log_file->write_report("Failover to next destination.", "t_abstract_dialog::failover_request"); t_request *req = cr->get_request(); // Get next destination if (!req->next_destination()) { log_file->write_report("No next destination for failover.", "t_abstract_dialog::failover_request"); return false; } resend_request(cr); return true; } //////////// // Public //////////// t_abstract_dialog::t_abstract_dialog(t_phone_user *pu) : t_id_object() { assert(pu); phone_user = pu; call_id_owner = false; local_seqnr = 0; remote_seqnr = 0; remote_seqnr_set = false; local_resp_nr = 0; remote_resp_nr = 0; remote_ip_port.clear(); log_file->write_header("t_abstract_dialog::t_abstract_dialog", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Created dialog, id="); log_file->write_raw(get_object_id()); log_file->write_endl(); log_file->write_footer(); } t_abstract_dialog::~t_abstract_dialog() { log_file->write_header("t_abstract_dialog::~t_abstract_dialog", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Destroy dialog, id="); log_file->write_raw(get_object_id()); log_file->write_endl(); log_file->write_footer(); } t_user *t_abstract_dialog::get_user(void) const { return phone_user->get_user_profile(); } void t_abstract_dialog::recvd_response(t_response *r, t_tuid tuid, t_tid tid) { // The source address and port of a message may be 0 when the // message was sent internally. if (!r->src_ip_port.is_null()) { remote_ip_port = r->src_ip_port; } } void t_abstract_dialog::recvd_request(t_request *r, t_tuid tuid, t_tid tid) { // The source address and port of a message may be 0 when the // message was sent internally. if (!r->src_ip_port.is_null()) { remote_ip_port = r->src_ip_port; } } bool t_abstract_dialog::match_response(t_response *r, t_tuid tuid) { return (call_id == r->hdr_call_id.call_id && local_tag == r->hdr_from.tag && (remote_tag.size() == 0 || remote_tag == r->hdr_to.tag)); } bool t_abstract_dialog::match_request(t_request *r) { return match(r->hdr_call_id.call_id, r->hdr_to.tag, r->hdr_from.tag); } bool t_abstract_dialog::match_partial_request(t_request *r) { return (r->hdr_call_id.call_id == call_id && r->hdr_to.tag == local_tag); } bool t_abstract_dialog::match(const string &_call_id, const string &to_tag, const string &from_tag) const { return (call_id == _call_id && local_tag == to_tag && remote_tag == from_tag); } t_url t_abstract_dialog::get_remote_target_uri(void) const { return remote_target_uri; } string t_abstract_dialog::get_remote_target_display(void) const { return remote_target_display; } t_url t_abstract_dialog::get_remote_uri(void) const { return remote_uri; } string t_abstract_dialog::get_remote_display(void) const { return remote_display; } t_ip_port t_abstract_dialog::get_remote_ip_port(void) const { return remote_ip_port; } string t_abstract_dialog::get_call_id(void) const { return call_id; } string t_abstract_dialog::get_local_tag(void) const { return local_tag; } string t_abstract_dialog::get_remote_tag(void) const { return remote_tag; } bool t_abstract_dialog::remote_extension_supported(const string &extension) const { return (remote_extensions.find(extension) != remote_extensions.end()); } bool t_abstract_dialog::is_call_id_owner(void) const { return call_id_owner; } twinkle-1.10.1/src/abstract_dialog.h000066400000000000000000000247661277565361200173660ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Abstract class for all types of SIP dialogs. */ #ifndef _ABSTRACT_DIALOG_H #define _ABSTRACT_DIALOG_H #include #include #include #include #include "client_request.h" #include "id_object.h" #include "protocol.h" #include "sockets/url.h" #include "parser/request.h" using namespace std; // Forward declaration class t_phone_user; /** * Abstract class for all types of SIP dialogs. * Concrete classes for all SIP dialogs inherit from this class. */ class t_abstract_dialog : public t_id_object { protected: /** * Phone user for which this dialog is created. * This pointer should never be deleted. */ t_phone_user *phone_user; string call_id; /**< SIP call id. */ bool call_id_owner; /**< Indicates if the call id is generated locally. */ string local_tag; /**< Local tag value. */ string remote_tag; /**< Remote tag value. */ unsigned long local_seqnr; /**< Last local sequence number issued. */ unsigned long remote_seqnr; /**< Last remote sequence number received. */ /** * The remote_seqnr_set indicates if the remote_seqnr is set by the far-end. * RFC 3261 allows the CSeq sequence number to be 0. So there is no * invalid sequence number. */ bool remote_seqnr_set; t_url local_uri; /**< URI of the local party (From/To headers). */ string local_display; /**< Display name of the local party. */ t_url remote_uri; /**< URI of the remote party (From/To headers). */ string remote_display; /**< Display name of the remote party. */ /** URI of the remote target (Contact header). This is the destination for a request. */ t_url remote_target_uri; string remote_target_display; /**< Display name of the remote target. */ list route_set; /**< The route set. */ unsigned long local_resp_nr; /**< Last local response number (for 100rel) issued. */ unsigned long remote_resp_nr; /**< Last remote response number (for 100rel) received. */ set remote_extensions; /**< SIP extensions supported by the remote party. */ /** The IP transport/address/port from which the last SIP message was received. */ t_ip_port remote_ip_port; /** * Remove a client request. Pass one of the client request * pointers to this member. The reference count of the * request will be decremented. If it becomes zero, then * the request object is deleted. * In all cases the passed pointer will be set to NULL. * @param cr [in] The client request. */ void remove_client_request(t_client_request **cr); /** * Create route set from the Record-Route header of a response. * If the response does not have a Record-Route header, then the route * set is cleared. * @param r [in] The response. */ void create_route_set(t_response *r); /** * Create remote target uri and display from the Contact header of a response. * @param r [in] The response. */ void create_remote_target(t_response *r); /** * Send a request within the dialog. * Sending a request will create a SIP transaction. * @param r [in] The request. * @param tuid [in] The transaction user id to be assigend to the transaction. */ virtual void send_request(t_request *r, t_tuid tuid) = 0; /** * Resend an existing client request. * A new Via and CSeq header will be put in the request. * Resending is different from retransmitting. Requests are automatically * retransmitted by the transaction layer. Resending creates a new SIP * transaction. Resending is f.i. done when a request must be redirected. * @param cr [in] The client request. */ virtual void resend_request(t_client_request *cr); /** * Resend mid-dialog request with an authorization header containing * credentials for the challenge in the response. * @param cr [in] The request. * @param resp [in] The 401 or 407 response. * @return true, if resending succeeded. * @return false, if credentials could not be determined. * * @pre The response must be a 401 or 407. */ bool resend_request_auth(t_client_request *cr, t_response *resp); /** * Redirect mid-dialog request to the next destination. * There are multiple reasons for redirection: * - A 3XX response was received. * - The request failed with a non-3XX response. A next contact should be tried. * * @param cr [in] The request. * @param resp [in] The failure response that was received on the request. * @param contact [out] Contains on successful return the contact to which the request is sent. * @return true, if the request is sent to a next destination. * @return false, if no next destination exists. */ bool redirect_request(t_client_request *cr, t_response *resp, t_contact_param &contact); /** * Failover request to the next destination from DNS lookup. * @param cr [in] The request. * @return true, if the request is sent to a next destination. * @return false, if no next destination exists. */ bool failover_request(t_client_request *cr); public: /** * Constructor. * @param pu [in] Phone user for which the dialog must be created. */ t_abstract_dialog(t_phone_user *pu); /** * Destructor. */ virtual ~t_abstract_dialog(); /** * Create a request using the stored dialog state information. * @param m [in] Request method. * @return The request. */ virtual t_request *create_request(t_method m); /** * Copy a dialog. * @return A copy of the dialog. */ virtual t_abstract_dialog *copy(void) = 0; /** * Get a pointer to the user profile of the user for whom this dialog * was created. * @return The user profile. */ t_user *get_user(void) const; /** * Resend mid-dialog request with an authorization header containing * credentials for the challenge in the response. * @param resp [in] The 401 or 407 response to the request that must be resent. * @return true, if resending succeeded. * @return false, if credentials could not be determined. * * @pre The response must be a 401 or 407. */ virtual bool resend_request_auth(t_response *resp) = 0; /** * Redirect mid-dialog request to the next destination. * @param resp [in] The response to the request that must be resent. * @return true, if the request is sent to a next destination. * @return false, if no next destination exists. */ virtual bool redirect_request(t_response *resp) = 0; /** * Failover request to the next destination from DNS lookup. * @param resp [in] The response to the request that must be resent. * @return true, if the request is sent to a next destination. * @return false, if no next destination exists. */ virtual bool failover_request(t_response *resp) = 0; /** * Process a received response. * @param r [in] The received response. * @param tuid [in] The transaction user id of the transaction for the response. * @param tid [in] The transaction id of the transaction for the response. */ virtual void recvd_response(t_response *r, t_tuid tuid, t_tid tid); /** * Process a received request. * @param r [in] The received request. * @param tuid [in] The transaction user id of the transaction for the request. * @param tid [in] The transaction id of the transaction for the request. */ virtual void recvd_request(t_request *r, t_tuid tuid, t_tid tid); /** * Match a response with the dialog. * @param r [in] The response. * @param tuid [in] The transaction user id of the transaction for the response. * @return true, if the response matches the dialog. * @return false, otherwise. */ virtual bool match_response(t_response *r, t_tuid tuid); /** * Match a request with the dialog. * @param r [in] The request. * @return true, if the request matches the dialog. * @return false, otherwise. */ virtual bool match_request(t_request *r); /** * Partially match a request with the dialog, i.e. do not match remote tag. * @param r [in] The request. * @return true, if the request partially matches the dialog. * @return false, otherwise. */ virtual bool match_partial_request(t_request *r); /** * Match call-id and tags with the dialog. * @param _call_id [in] SIP call-id. * @param to_tag [in] SIP to-tag. * @param from_tag [in] SIP from-tag. * @return true, if call-id and tags match the dialog. * @return false, otherwise. */ virtual bool match(const string &_call_id, const string &to_tag, const string &from_tag) const; /** * Get the URI of the remote target. * @return remote target URI. * @see remote_target_uri */ t_url get_remote_target_uri(void) const; /** * Get the display name of the remote target. * @return display name of remote target. * @see remote_target_display */ string get_remote_target_display(void) const; /** * Get the URI of the remote party. * @return URI of remote party. * @see remote_uri */ t_url get_remote_uri(void) const; /** * Get the display name of the remote party. * @return display name of the remote party. * @see remote_display */ string get_remote_display(void) const; /** * Get the IP transport/address/port from which the last SIP message was received. * @return transport/address/port */ t_ip_port get_remote_ip_port(void) const; /** * Get the SIP call id. * @return SIP call id. */ string get_call_id(void) const; /** * Get the local tag. * @return local tag. */ string get_local_tag(void) const; /** * Get the remote tag. * @return remote tag. */ string get_remote_tag(void) const; /** * Check if the remote party supports a particular SIP exentsion. * @param extension [in] Name of the SIP extension. * @return true, if remote party supports the extension. * @return false, otherwise. */ virtual bool remote_extension_supported(const string &extension) const; /** * Check if this dialog is the owner of the call id. * @return true, if this dialog is the owner. * @return false, otherwise. * @see call_id_owner */ bool is_call_id_owner(void) const; }; #endif twinkle-1.10.1/src/address_book.cpp000066400000000000000000000103641277565361200172230ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "address_book.h" #include "sys_settings.h" #include "translator.h" #include "userintf.h" #include "util.h" // Call history file #define ADDRESS_BOOK_FILE "twinkle.ab"; // Field seperator in call history file #define REC_SEPARATOR '|' //////////////////////////// // class t_address_card //////////////////////////// string t_address_card::get_display_name(void) const { string s; if (!name_first.empty()) { s = name_first; } if (!name_infix.empty()) { if (!s.empty()) s += ' '; s += name_infix; } if (!name_last.empty()) { if (!s.empty()) s += ' '; s += name_last; } return s; } bool t_address_card::create_file_record(vector &v) const { v.clear(); v.push_back(name_first); v.push_back(name_infix); v.push_back(name_last); v.push_back(sip_address); v.push_back(remark); return true; } bool t_address_card::populate_from_file_record(const vector &v) { // Check number of fields if (v.size() != 5) return false; name_first = v[0]; name_infix = v[1]; name_last = v[2]; sip_address = v[3]; remark = v[4]; return true; } bool t_address_card::operator==(const t_address_card other) const { return (name_first == other.name_first && name_infix == other.name_infix && name_last == other.name_last && sip_address == other.sip_address && remark == other.remark); } //////////////////////////// // class t_address_book //////////////////////////// // Private void t_address_book::find_address(t_user *user_config, const t_url &u) const { if (u == last_url) return; last_url = u; last_name.clear(); // Normalize url using number conversion rules t_url u_normalized(u); u_normalized.apply_conversion_rules(user_config); for (list::const_iterator i = records.begin(); i != records.end(); i++) { string full_address = ui->expand_destination(user_config, i->sip_address, u_normalized.get_scheme()); t_url url_phone(full_address); if (!url_phone.is_valid()) continue; if (u_normalized.user_host_match(url_phone, user_config->get_remove_special_phone_symbols(), user_config->get_special_phone_symbols())) { last_name = i->get_display_name(); return; } } } // Public t_address_book::t_address_book() : utils::t_record_file() { set_header("first_name|infix_name|last_name|sip_address|remark"); set_separator(REC_SEPARATOR); string s(DIR_HOME); s += "/"; s += USER_DIR; s += "/"; s += ADDRESS_BOOK_FILE; set_filename(s); } void t_address_book::add_address(const t_address_card &address) { mtx_records.lock(); records.push_back(address); mtx_records.unlock(); } bool t_address_book::del_address(const t_address_card &address) { mtx_records.lock(); list::iterator it = find(records.begin(), records.end(), address); if (it == records.end()) { mtx_records.unlock(); return false; } records.erase(it); // Invalidate the cache for the address finder last_url.set_url(""); mtx_records.unlock(); return true; } bool t_address_book::update_address(const t_address_card &old_address, const t_address_card &new_address) { mtx_records.lock(); if (!del_address(old_address)) { mtx_records.unlock(); return false; } records.push_back(new_address); mtx_records.unlock(); return true; } string t_address_book::find_name(t_user *user_config, const t_url &u) const { mtx_records.lock(); find_address(user_config, u); string name = last_name; mtx_records.unlock(); return name; } const list &t_address_book::get_address_list(void) const { return records; } twinkle-1.10.1/src/address_book.h000066400000000000000000000066341277565361200166750ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Local address book. */ #ifndef _ADDRESS_BOOK_H #define _ADDRESS_BOOK_H #include #include #include "user.h" #include "sockets/url.h" #include "threads/mutex.h" #include "utils/record_file.h" using namespace std; /** A single address card. */ class t_address_card : public utils::t_record { public: string name_last; /**< Last name. */ string name_first; /**< First name. */ string name_infix; /**< Infix name. */ string sip_address; /**< SIP address. */ string remark; /**< Remark. */ /** * Get the display name derived from first, last and infix name. * @return The display name. */ string get_display_name(void) const; virtual bool create_file_record(vector &v) const; virtual bool populate_from_file_record(const vector &v); /** Equality check. */ bool operator==(const t_address_card other) const; }; /** * A book containing address cards. The user can * create different address books. */ class t_address_book : public utils::t_record_file { private: /** @name Cache for last searched name/url mapping */ //@{ mutable t_url last_url; /**< Last URL. */ mutable string last_name; /**< Lat name. */ //@} /** * Find a matching address for a url and cache the display name. * @param user_config [in] The user profile. * @param u [in] The url to find. * @post If a matching address is found, then the URL and name are * put in the cache. Otherwise the cache is cleared. */ void find_address(t_user *user_config, const t_url &u) const; public: /** Constructor. */ t_address_book(); /** * Add an address. * @param address [in] The address to be added. */ void add_address(const t_address_card &address); /** * Delete an address. * @return true, if the address was successfully deleted. * @return false, if the address does not exist. */ bool del_address(const t_address_card &address); /** * Update an address. * @param old_address [in] The address to be updated. * @param new_address [in] The updated address information. * @return true, if the update was successful. * @return false, if the old address does not exist. */ bool update_address(const t_address_card &old_address, const t_address_card &new_address); /** * Find the display name for a SIP URL. * @param user_config [in] The user profile. * @param u [in] The SIP URL. * @return The display name if a match was found. * @return Empty string if no match can be found. */ string find_name(t_user *user_config, const t_url &u) const; /** * Get the list of addresses. * @return The list of addresses. */ const list &get_address_list(void) const; }; extern t_address_book *ab_local; #endif twinkle-1.10.1/src/audio/000077500000000000000000000000001277565361200151555ustar00rootroot00000000000000twinkle-1.10.1/src/audio/CMakeLists.txt000066400000000000000000000006721277565361200177220ustar00rootroot00000000000000project(libtwinkle-audio) set(LIBTWINKLE_AUDIO-SRCS audio_device.cpp audio_decoder.cpp audio_encoder.cpp audio_codecs.cpp audio_rx.cpp audio_session.cpp audio_tx.cpp dtmf_player.cpp freq_gen.cpp g711.cpp g721.cpp g723_16.cpp g723_24.cpp g723_40.cpp g72x.cpp media_buffer.cpp rtp_telephone_event.cpp tone_gen.cpp twinkle_rtp_session.cpp twinkle_zrtp_ui.cpp ) add_library(libtwinkle-audio OBJECT ${LIBTWINKLE_AUDIO-SRCS}) twinkle-1.10.1/src/audio/README_G711000066400000000000000000000062211277565361200165350ustar00rootroot00000000000000The files in this directory comprise ANSI-C language reference implementations of the CCITT (International Telegraph and Telephone Consultative Committee) G.711, G.721 and G.723 voice compressions. They have been tested on Sun SPARCstations and passed 82 out of 84 test vectors published by CCITT (Dec. 20, 1988) for G.721 and G.723. [The two remaining test vectors, which the G.721 decoder implementation for u-law samples did not pass, may be in error because they are identical to two other vectors for G.723_40.] This source code is released by Sun Microsystems, Inc. to the public domain. Please give your acknowledgement in product literature if this code is used in your product implementation. Sun Microsystems supports some CCITT audio formats in Solaris 2.0 system software. However, Sun's implementations have been optimized for higher performance on SPARCstations. The source files for CCITT conversion routines in this directory are: g72x.h header file for g721.c, g723_24.c and g723_40.c g711.c CCITT G.711 u-law and A-law compression g72x.c common denominator of G.721 and G.723 ADPCM codes g721.c CCITT G.721 32Kbps ADPCM coder (with g72x.c) g723_24.c CCITT G.723 24Kbps ADPCM coder (with g72x.c) g723_40.c CCITT G.723 40Kbps ADPCM coder (with g72x.c) Simple conversions between u-law, A-law, and 16-bit linear PCM are invoked as follows: unsigned char ucode, acode; short pcm_val; ucode = linear2ulaw(pcm_val); ucode = alaw2ulaw(acode); acode = linear2alaw(pcm_val); acode = ulaw2alaw(ucode); pcm_val = ulaw2linear(ucode); pcm_val = alaw2linear(acode); The other CCITT compression routines are invoked as follows: #include "g72x.h" struct g72x_state state; int sample, code; g72x_init_state(&state); code = {g721,g723_24,g723_40}_encoder(sample, coding, &state); sample = {g721,g723_24,g723_40}_decoder(code, coding, &state); where coding = AUDIO_ENCODING_ULAW for 8-bit u-law samples AUDIO_ENCODING_ALAW for 8-bit A-law samples AUDIO_ENCODING_LINEAR for 16-bit linear PCM samples This directory also includes the following sample programs: encode.c CCITT ADPCM encoder decode.c CCITT ADPCM decoder Makefile makefile for the sample programs The sample programs contain examples of how to call the various compression routines and pack/unpack the bits. The sample programs read byte streams from stdin and write to stdout. The input/output data is raw data (no file header or other identifying information is embedded). The sample programs are invoked as follows: encode [-3|4|5] [-a|u|l] outfile decode [-3|4|5] [-a|u|l] outfile where: -3 encode to (decode from) G.723 24kbps (3-bit) data -4 encode to (decode from) G.721 32kbps (4-bit) data [the default] -5 encode to (decode from) G.723 40kbps (5-bit) data -a encode from (decode to) A-law data -u encode from (decode to) u-law data [the default] -l encode from (decode to) 16-bit linear data Examples: # Read 16-bit linear and output G.721 encode -4 -l g721file # Read 40Kbps G.723 and output A-law decode -5 -a alawfile # Compress and then decompress u-law data using 24Kbps G.723 encode -3 ulawout twinkle-1.10.1/src/audio/audio_codecs.cpp000066400000000000000000000055041277565361200203060ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include "audio_codecs.h" unsigned short audio_sample_rate(t_audio_codec codec) { switch(codec) { case CODEC_G711_ALAW: case CODEC_G711_ULAW: case CODEC_GSM: case CODEC_SPEEX_NB: case CODEC_ILBC: case CODEC_G729A: case CODEC_G726_16: case CODEC_G726_24: case CODEC_G726_32: case CODEC_G726_40: case CODEC_TELEPHONE_EVENT: return 8000; case CODEC_SPEEX_WB: return 16000; case CODEC_SPEEX_UWB: return 32000; default: // Use 8000 as default rate return 8000; } } bool is_speex_codec(t_audio_codec codec) { return (codec == CODEC_SPEEX_NB || codec == CODEC_SPEEX_WB || codec == CODEC_SPEEX_UWB); } int resample(short *input_buf, int input_len, int input_sample_rate, short *output_buf, int output_len, int output_sample_rate) { if (input_sample_rate > output_sample_rate) { int downsample_factor = input_sample_rate / output_sample_rate; int output_idx = -1; for (int i = 0; i < input_len; i += downsample_factor) { output_idx = i / downsample_factor; if (output_idx >= output_len) { // Output buffer is full return output_len; } output_buf[output_idx] = input_buf[i]; } return output_idx + 1; } else { int upsample_factor = output_sample_rate / input_sample_rate; int output_idx = -1; for (int i = 0; i < input_len; i++) { for (int j = 0; j < upsample_factor; j++) { output_idx = i * upsample_factor + j; if (output_idx >= output_len) { // Output buffer is full return output_len; } output_buf[output_idx] = input_buf[i]; } } return output_idx + 1; } } short mix_linear_pcm(short pcm1, short pcm2) { long mixed_sample = long(pcm1) + long(pcm2); // Compress a 17 bit PCM value into a 16-bit value. // The easy way is to divide the value by 2, but this lowers // the volume. // Only lower the volume for the loud values. As for a normal // voice call the values are not that loud, this gives better // quality. if (mixed_sample > 16384) { mixed_sample = 16384 + (mixed_sample - 16384) / 3; } else if (mixed_sample < -16384) { mixed_sample = -16384 - (-16384 - mixed_sample) / 3; } return short(mixed_sample); } twinkle-1.10.1/src/audio/audio_codecs.h000066400000000000000000000064671277565361200177640ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _AUDIO_CODECS_H #define _AUDIO_CODECS_H #include "g711.h" #include "g72x.h" // Audio codecs enum t_audio_codec { CODEC_NULL, CODEC_UNSUPPORTED, CODEC_G711_ALAW, CODEC_G711_ULAW, CODEC_GSM, CODEC_SPEEX_NB, CODEC_SPEEX_WB, CODEC_SPEEX_UWB, CODEC_ILBC, CODEC_G726_16, CODEC_G726_24, CODEC_G726_32, CODEC_G726_40, CODEC_TELEPHONE_EVENT, CODEC_G729A }; // Default ptime values (ms) for audio codecs #define PTIME_G711_ALAW 20 #define PTIME_G711_ULAW 20 #define PTIME_G726 20 #define PTIME_GSM 20 #define PTIME_SPEEX 20 #define MIN_PTIME 10 #define MAX_PTIME 80 // Audio sample settings #define AUDIO_SAMPLE_SIZE 16 // Maximum length (in packets) for concealment of lost packets #define MAX_CONCEALMENT 2 // Size of jitter buffer in ms // The jitter buffer is used to smooth playing out incoming RTP packets. // The size of the buffer is also used as the expiry time in the ccRTP // stack. Packets that have timestamp that is older than then size of // the jitter buffer will not be sent out anymore. #define JITTER_BUF_MS 80 // Duration of the expiry timer in the RTP stack. // The ccRTP stack checks all data delivered to it against its clock. // If the data is too old it will not send it out. Data can be old // for several reasons: // // 1) The thread reading the soundcard has been paused for a while // 2) The audio card buffers sound before releasing it. // // Especially the latter seems to happen on some soundcards. Data // not older than defined delay are still allowed to go out. It's up // to the receiving and to deal with the jitter this may cause. #define MAX_OUT_AUDIO_DELAY_MS 160 // Buffer sizes #define JITTER_BUF_SIZE(sample_rate) (JITTER_BUF_MS * (sample_rate)/1000 * AUDIO_SAMPLE_SIZE/8) // Log speex errors #define LOG_SPEEX_ERROR(func, spxfunc, spxerr) {\ log_file->write_header((func), LOG_NORMAL, LOG_DEBUG);\ log_file->write_raw("Speex error: ");\ log_file->write_raw((spxfunc));\ log_file->write_raw(" returned ");\ log_file->write_raw((spxerr));\ log_file->write_footer(); } // Return the sampling rate for a codec unsigned short audio_sample_rate(t_audio_codec codec); // Returns true if the codec is a speex codec bool is_speex_codec(t_audio_codec codec); // Resample the input buffer to the output buffer // Returns the number of samples put in the output buffer // If the output buffer is too small, the number of samples will be // truncated. int resample(short *input_buf, int input_len, int input_sample_rate, short *output_buf, int output_len, int output_sample_rate); // Mix 2 16 bits signed linear PCM values short mix_linear_pcm(short pcm1, short pcm2); #endif twinkle-1.10.1/src/audio/audio_decoder.cpp000066400000000000000000000337131277565361200204560ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include "audio_decoder.h" #include "log.h" #ifdef HAVE_ILBC #ifndef HAVE_ILBC_CPP extern "C" { #endif #include #ifndef HAVE_ILBC_CPP } #endif #endif ////////////////////////////////////////// // class t_audio_decoder ////////////////////////////////////////// t_audio_decoder::t_audio_decoder(uint16 default_ptime, bool plc, t_user *user_config) : _default_ptime(default_ptime), _plc(plc), _user_config(user_config) {} t_audio_codec t_audio_decoder::get_codec(void) const { return _codec; } uint16 t_audio_decoder::get_default_ptime(void) const { return _default_ptime; } bool t_audio_decoder::has_plc(void) const { return _plc; } uint16 t_audio_decoder::conceal(int16 *pcm_buf, uint16 pcm_buf_size) { return 0; } ////////////////////////////////////////// // class t_g711a_audio_decoder ////////////////////////////////////////// t_g711a_audio_decoder::t_g711a_audio_decoder(uint16 default_ptime, t_user *user_config) : t_audio_decoder(default_ptime, false, user_config) { _codec = CODEC_G711_ALAW; if (default_ptime == 0) { _default_ptime = PTIME_G711_ALAW; } } uint16 t_g711a_audio_decoder::get_ptime(uint16 payload_size) const { return payload_size / (audio_sample_rate(_codec) / 1000); } uint16 t_g711a_audio_decoder::decode(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size) { assert(pcm_buf_size >= payload_size); for (int i = 0; i < payload_size; i++) { pcm_buf[i] = alaw2linear(payload[i]); } return payload_size; } bool t_g711a_audio_decoder::valid_payload_size(uint16 payload_size, uint16 sample_buf_size) const { return payload_size <= sample_buf_size; } ////////////////////////////////////////// // class t_g711u_audio_decoder ////////////////////////////////////////// t_g711u_audio_decoder::t_g711u_audio_decoder(uint16 default_ptime, t_user *user_config) : t_audio_decoder(default_ptime, false, user_config) { _codec = CODEC_G711_ULAW; if (default_ptime == 0) { _default_ptime = PTIME_G711_ULAW; } } uint16 t_g711u_audio_decoder::get_ptime(uint16 payload_size) const { return payload_size / (audio_sample_rate(_codec) / 1000); } uint16 t_g711u_audio_decoder::decode(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size) { assert(pcm_buf_size >= payload_size); for (int i = 0; i < payload_size; i++) { pcm_buf[i] = ulaw2linear(payload[i]); } return payload_size; } bool t_g711u_audio_decoder::valid_payload_size(uint16 payload_size, uint16 sample_buf_size) const { return payload_size <= sample_buf_size; } ////////////////////////////////////////// // class t_gsm_audio_decoder ////////////////////////////////////////// t_gsm_audio_decoder::t_gsm_audio_decoder(t_user *user_config) : t_audio_decoder(PTIME_GSM, false, user_config) { _codec = CODEC_GSM; gsm_decoder = gsm_create(); } t_gsm_audio_decoder::~t_gsm_audio_decoder() { gsm_destroy(gsm_decoder); } uint16 t_gsm_audio_decoder::get_ptime(uint16 payload_size) const { return get_default_ptime(); } uint16 t_gsm_audio_decoder::decode(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size) { assert(pcm_buf_size >= 160); gsm_decode(gsm_decoder, payload, pcm_buf); return 160; } bool t_gsm_audio_decoder::valid_payload_size(uint16 payload_size, uint16 sample_buf_size) const { return payload_size == 33; } #ifdef HAVE_SPEEX ////////////////////////////////////////// // class t_speex_audio_decoder ////////////////////////////////////////// t_speex_audio_decoder::t_speex_audio_decoder(t_mode mode, t_user *user_config) : t_audio_decoder(0, true, user_config) { speex_bits_init(&speex_bits); _mode = mode; switch (mode) { case MODE_NB: _codec = CODEC_SPEEX_NB; speex_dec_state = speex_decoder_init(&speex_nb_mode); break; case MODE_WB: _codec = CODEC_SPEEX_WB; speex_dec_state = speex_decoder_init(&speex_wb_mode); break; case MODE_UWB: _codec = CODEC_SPEEX_UWB; speex_dec_state = speex_decoder_init(&speex_uwb_mode); break; default: assert(false); } int frame_size = 0; speex_decoder_ctl(speex_dec_state, SPEEX_GET_FRAME_SIZE, &frame_size); // Initialize decoder with user settings int arg = (user_config->get_speex_penh() ? 1 : 0); speex_decoder_ctl(speex_dec_state, SPEEX_SET_ENH, &arg); _default_ptime = frame_size / (audio_sample_rate(_codec) / 1000); } t_speex_audio_decoder::~t_speex_audio_decoder() { speex_bits_destroy(&speex_bits); speex_decoder_destroy(speex_dec_state); } uint16 t_speex_audio_decoder::get_ptime(uint16 payload_size) const { return get_default_ptime(); } uint16 t_speex_audio_decoder::decode(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size) { int retval; int speex_frame_size; speex_decoder_ctl(speex_dec_state, SPEEX_GET_FRAME_SIZE, &speex_frame_size); assert(pcm_buf_size >= speex_frame_size); speex_bits_read_from(&speex_bits, reinterpret_cast(payload), payload_size); retval = speex_decode_int(speex_dec_state, &speex_bits, pcm_buf); if (retval < 0) { LOG_SPEEX_ERROR("t_speex_audio_decoder::decode", "speex_decode_int", retval); return 0; } return speex_frame_size; } uint16 t_speex_audio_decoder::conceal(int16 *pcm_buf, uint16 pcm_buf_size) { int retval; int speex_frame_size; speex_decoder_ctl(speex_dec_state, SPEEX_GET_FRAME_SIZE, &speex_frame_size); assert(pcm_buf_size >= speex_frame_size); retval = speex_decode_int(speex_dec_state, NULL, pcm_buf); if (retval < 0) { LOG_SPEEX_ERROR("t_speex_audio_decoder::conceal", "speex_decode_int", retval); return 0; } return speex_frame_size; } bool t_speex_audio_decoder::valid_payload_size(uint16 payload_size, uint16 sample_buf_size) const { return true; } #endif #ifdef HAVE_ILBC ////////////////////////////////////////// // class t_ilbc_audio_decoder ////////////////////////////////////////// t_ilbc_audio_decoder::t_ilbc_audio_decoder(uint16 default_ptime, t_user *user_config) : t_audio_decoder(default_ptime, true, user_config) { _codec = CODEC_ILBC; _last_received_ptime = 0; initDecode(&_ilbc_decoder_20, 20, 1); initDecode(&_ilbc_decoder_30, 30, 1); } uint16 t_ilbc_audio_decoder::get_ptime(uint16 payload_size) const { if (payload_size == NO_OF_BYTES_20MS) { return 20; } else { return 30; } } uint16 t_ilbc_audio_decoder::decode(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size) { float sample; float block[BLOCKL_MAX]; int block_len; if (get_ptime(payload_size) == 20) { block_len = BLOCKL_20MS; assert(pcm_buf_size >= block_len); iLBC_decode(block, (unsigned char*)payload, &_ilbc_decoder_20, 1); _last_received_ptime = 20; } else { block_len = BLOCKL_30MS; assert(pcm_buf_size >= block_len); iLBC_decode(block, (unsigned char*)payload, &_ilbc_decoder_30, 1); _last_received_ptime = 30; } for (int i = 0; i < block_len; i++) { sample = block[i]; if (sample < MIN_SAMPLE) sample = MIN_SAMPLE; if (sample > MAX_SAMPLE) sample = MAX_SAMPLE; pcm_buf[i] = static_cast(sample); } return block_len; } uint16 t_ilbc_audio_decoder::conceal(int16 *pcm_buf, uint16 pcm_buf_size) { float sample; float block[BLOCKL_MAX]; int block_len; if (_last_received_ptime == 0) return 0; if (_last_received_ptime == 20) { block_len = BLOCKL_20MS; assert(pcm_buf_size >= block_len); iLBC_decode(block, NULL, &_ilbc_decoder_20, 0); } else { block_len = BLOCKL_30MS; assert(pcm_buf_size >= block_len); iLBC_decode(block, NULL, &_ilbc_decoder_30, 0); } for (int i = 0; i < block_len; i++) { sample = block[i]; if (sample < MIN_SAMPLE) sample = MIN_SAMPLE; if (sample > MAX_SAMPLE) sample = MAX_SAMPLE; pcm_buf[i] = static_cast(sample); } return block_len; } bool t_ilbc_audio_decoder::valid_payload_size(uint16 payload_size, uint16 sample_buf_size) const { return payload_size == NO_OF_BYTES_20MS || payload_size == NO_OF_BYTES_30MS; } #endif ////////////////////////////////////////// // class t_g726_audio_decoder ////////////////////////////////////////// t_g726_audio_decoder::t_g726_audio_decoder(t_bit_rate bit_rate, uint16 default_ptime, t_user *user_config) : t_audio_decoder(default_ptime, false, user_config) { _bit_rate = bit_rate; if (default_ptime == 0) { _default_ptime = PTIME_G726; } switch (_bit_rate) { case BIT_RATE_16: _codec = CODEC_G726_16; _bits_per_sample = 2; break; case BIT_RATE_24: _codec = CODEC_G726_24; _bits_per_sample = 3; break; case BIT_RATE_32: _codec = CODEC_G726_32; _bits_per_sample = 4; break; case BIT_RATE_40: _codec = CODEC_G726_40; _bits_per_sample = 5; break; default: assert(false); } _packing = user_config->get_g726_packing(); g72x_init_state(&_state); } uint16 t_g726_audio_decoder::get_ptime(uint16 payload_size) const { return (payload_size * 8 / _bits_per_sample) / (audio_sample_rate(_codec) / 1000); } uint16 t_g726_audio_decoder::decode_16(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size) { assert(payload_size * 4 <= pcm_buf_size); for (int i = 0; i < payload_size; i++) { for (int j = 0; j < 4; j++) { uint8 w; if (_packing == G726_PACK_RFC3551) { w = (payload[i] >> (j*2)) & 0x3; } else { w = (payload[i] >> ((3-j)*2)) & 0x3; } pcm_buf[4*i+j] = g723_16_decoder( w, AUDIO_ENCODING_LINEAR, &_state); } } return payload_size * 4; } uint16 t_g726_audio_decoder::decode_24(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size) { assert(payload_size % 3 == 0); assert(payload_size * 8 / 3 <= pcm_buf_size); for (int i = 0; i < payload_size; i += 3) { uint32 v = (static_cast(payload[i+2]) << 16) | (static_cast(payload[i+1]) << 8) | static_cast(payload[i]); for (int j = 0; j < 8; j++) { uint8 w; if (_packing == G726_PACK_RFC3551) { w = (v >> (j*3)) & 0x7; } else { w = (v >> ((7-j)*3)) & 0x7; } pcm_buf[8*(i/3)+j] = g723_24_decoder( w, AUDIO_ENCODING_LINEAR, &_state); } } return payload_size * 8 / 3; } uint16 t_g726_audio_decoder::decode_32(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size) { assert(payload_size * 2 <= pcm_buf_size); for (int i = 0; i < payload_size; i++) { for (int j = 0; j < 2; j++) { uint8 w; if (_packing == G726_PACK_RFC3551) { w = (payload[i] >> (j*4)) & 0xf; } else { w = (payload[i] >> ((1-j)*4)) & 0xf; } pcm_buf[2*i+j] = g721_decoder( w, AUDIO_ENCODING_LINEAR, &_state); } } return payload_size * 2; } uint16 t_g726_audio_decoder::decode_40(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size) { assert(payload_size % 5 == 0); assert(payload_size * 8 / 5 <= pcm_buf_size); for (int i = 0; i < payload_size; i += 5) { uint64 v = (static_cast(payload[i+4]) << 32) | (static_cast(payload[i+3]) << 24) | (static_cast(payload[i+2]) << 16) | (static_cast(payload[i+1]) << 8) | static_cast(payload[i]); for (int j = 0; j < 8; j++) { uint8 w; if (_packing == G726_PACK_RFC3551) { w = (v >> (j*5)) & 0x1f; } else { w = (v >> ((7-j)*5)) & 0x1f; } pcm_buf[8*(i/5)+j] = g723_40_decoder( w, AUDIO_ENCODING_LINEAR, &_state); } } return payload_size * 8 / 5; } uint16 t_g726_audio_decoder::decode(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size) { switch (_bit_rate) { case BIT_RATE_16: return decode_16(payload, payload_size, pcm_buf, pcm_buf_size); break; case BIT_RATE_24: return decode_24(payload, payload_size, pcm_buf, pcm_buf_size); break; case BIT_RATE_32: return decode_32(payload, payload_size, pcm_buf, pcm_buf_size); break; case BIT_RATE_40: return decode_40(payload, payload_size, pcm_buf, pcm_buf_size); break; default: assert(false); } return 0; } bool t_g726_audio_decoder::valid_payload_size(uint16 payload_size, uint16 sample_buf_size) const { switch (_bit_rate) { case BIT_RATE_24: // Payload size must be multiple of 3 if (payload_size % 3 != 0) return false; break; case BIT_RATE_40: // Payload size must be multiple of 5 if (payload_size % 5 != 0) return false; break; default: break; } return (payload_size * 8 / _bits_per_sample ) <= sample_buf_size; } #ifdef HAVE_BCG729 t_g729a_audio_decoder::t_g729a_audio_decoder(uint16 default_ptime, t_user *user_config) : t_audio_decoder(default_ptime, true, user_config) { _context = initBcg729DecoderChannel(); } t_g729a_audio_decoder::~t_g729a_audio_decoder() { closeBcg729DecoderChannel(_context); } uint16 t_g729a_audio_decoder::get_ptime(uint16 payload_size) const { return (payload_size * 8) / (audio_sample_rate(_codec) / 1000); } uint16 t_g729a_audio_decoder::decode(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size) { assert((payload_size % 10) == 0); assert(pcm_buf_size >= payload_size*8); for (uint16 done = 0; done < payload_size; done += 10) { bcg729Decoder(_context, &payload[done], false, &pcm_buf[done * 8]); } return payload_size * 8; } bool t_g729a_audio_decoder::valid_payload_size(uint16 payload_size, uint16 sample_buf_size) const { return payload_size > 0 && (payload_size % 10) == 0; } uint16 t_g729a_audio_decoder::conceal(int16 *pcm_buf, uint16 pcm_buf_size) { assert(pcm_buf_size >= 80); bcg729Decoder(_context, nullptr, true, pcm_buf); return 80; } #endif twinkle-1.10.1/src/audio/audio_decoder.h000066400000000000000000000147261277565361200201260ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Audio decoders #ifndef _AUDIO_DECODER_H #define _AUDIO_DECODER_H #include "twinkle_config.h" #include "audio_codecs.h" #include "user.h" #ifdef HAVE_GSM extern "C" { #include } #else #include "gsm/inc/gsm.h" #endif #ifdef HAVE_SPEEX #include #include #endif #ifdef HAVE_BCG729 extern "C" { # include } #endif #ifdef HAVE_ILBC #ifndef HAVE_ILBC_CPP extern "C" { #endif #include #ifndef HAVE_ILBC_CPP } #endif #endif // Abstract definition of an audio decoder class t_audio_decoder { protected: t_audio_codec _codec; uint16 _default_ptime; bool _plc; // packet loss concealment t_user *_user_config; t_audio_decoder(uint16 default_ptime, bool plc, t_user *user_config); public: virtual ~t_audio_decoder() {}; t_audio_codec get_codec(void) const; uint16 get_default_ptime(void) const; virtual uint16 get_ptime(uint16 payload_size) const = 0; // Decode a buffer of encoded samples to 16-bit PCM. // Returns the number of pcm samples written into pcm_buf // Returns 0 if decoding failed virtual uint16 decode(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size) = 0; // Indicates if codec has PLC algorithm bool has_plc(void) const; // Create a payload to conceal a lost packet. // Returns the number of pcm samples written into pcm_buf // Returns 0 if decoding failed virtual uint16 conceal(int16 *pcm_buf, uint16 pcm_buf_size); // Determine if the payload size is valid for this decoder virtual bool valid_payload_size(uint16 payload_size, uint16 sample_buf_size) const = 0; }; // G.711 A-law class t_g711a_audio_decoder : public t_audio_decoder { public: t_g711a_audio_decoder(uint16 default_ptime, t_user *user_config); virtual uint16 get_ptime(uint16 payload_size) const; virtual uint16 decode(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size); virtual bool valid_payload_size(uint16 payload_size, uint16 sample_buf_size) const; }; // G.711 u-law class t_g711u_audio_decoder : public t_audio_decoder { public: t_g711u_audio_decoder(uint16 default_ptime, t_user *user_config); virtual uint16 get_ptime(uint16 payload_size) const; virtual uint16 decode(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size); virtual bool valid_payload_size(uint16 payload_size, uint16 sample_buf_size) const; }; // GSM class t_gsm_audio_decoder : public t_audio_decoder { private: gsm gsm_decoder; public: t_gsm_audio_decoder(t_user *user_config); virtual ~t_gsm_audio_decoder(); virtual uint16 get_ptime(uint16 payload_size) const; virtual uint16 decode(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size); virtual bool valid_payload_size(uint16 payload_size, uint16 sample_buf_size) const; }; #ifdef HAVE_SPEEX // Speex class t_speex_audio_decoder : public t_audio_decoder { public: enum t_mode { MODE_NB, // Narrow band MODE_WB, // Wide band MODE_UWB // Ultra wide band }; private: SpeexBits speex_bits; void *speex_dec_state; t_mode _mode; public: t_speex_audio_decoder(t_mode mode, t_user *user_config); virtual ~t_speex_audio_decoder(); virtual uint16 get_ptime(uint16 payload_size) const; virtual uint16 decode(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size); virtual uint16 conceal(int16 *pcm_buf, uint16 pcm_buf_size); virtual bool valid_payload_size(uint16 payload_size, uint16 sample_buf_size) const; }; #endif #ifdef HAVE_ILBC // iLBC class t_ilbc_audio_decoder : public t_audio_decoder { private: iLBC_Dec_Inst_t _ilbc_decoder_20; // decoder for 20ms frames iLBC_Dec_Inst_t _ilbc_decoder_30; // decoder for 30ms frames // The number of ms received in the last frame, so the conceal function // can determine which decoder to use to conceal a lost frame. int _last_received_ptime; public: t_ilbc_audio_decoder(uint16 default_ptime, t_user *user_config); virtual uint16 get_ptime(uint16 payload_size) const; virtual uint16 decode(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size); virtual uint16 conceal(int16 *pcm_buf, uint16 pcm_buf_size); virtual bool valid_payload_size(uint16 payload_size, uint16 sample_buf_size) const; }; #endif // G.726 class t_g726_audio_decoder : public t_audio_decoder { public: enum t_bit_rate { BIT_RATE_16, BIT_RATE_24, BIT_RATE_32, BIT_RATE_40 }; private: uint16 decode_16(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size); uint16 decode_24(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size); uint16 decode_32(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size); uint16 decode_40(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size); struct g72x_state _state; t_bit_rate _bit_rate; uint8 _bits_per_sample; t_g726_packing _packing; public: t_g726_audio_decoder(t_bit_rate bit_rate, uint16 default_ptime, t_user *user_config); virtual uint16 get_ptime(uint16 payload_size) const; virtual uint16 decode(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size); virtual bool valid_payload_size(uint16 payload_size, uint16 sample_buf_size) const; }; #ifdef HAVE_BCG729 // G.729A class t_g729a_audio_decoder : public t_audio_decoder { public: t_g729a_audio_decoder(uint16 default_ptime, t_user *user_config); ~t_g729a_audio_decoder(); virtual uint16 get_ptime(uint16 payload_size) const override; virtual uint16 decode(uint8 *payload, uint16 payload_size, int16 *pcm_buf, uint16 pcm_buf_size) override; virtual bool valid_payload_size(uint16 payload_size, uint16 sample_buf_size) const override; virtual uint16 conceal(int16 *pcm_buf, uint16 pcm_buf_size) override; private: bcg729DecoderChannelContextStruct* _context; }; #endif #endif twinkle-1.10.1/src/audio/audio_device.cpp000066400000000000000000000636651277565361200203210ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "audio_device.h" #include #include #include #include #include #include #include "sys_settings.h" #include "translator.h" #include "log.h" #include "userintf.h" #include "util.h" #include "audits/memman.h" #ifdef HAVE_LIBASOUND #include #endif t_audio_io* t_audio_io::open(const t_audio_device& dev, bool playback, bool capture, bool blocking, int channels, t_audio_sampleformat format, int sample_rate, bool short_latency) { t_audio_io* aio; if(dev.type == t_audio_device::OSS) { aio = new t_oss_io(); MEMMAN_NEW(aio); #ifdef HAVE_LIBASOUND } else if (dev.type == t_audio_device::ALSA) { aio = new t_alsa_io(); MEMMAN_NEW(aio); #endif } else { string msg("Audio device not implemented"); log_file->write_report(msg, "t_audio_io::open", LOG_NORMAL, LOG_CRITICAL); return 0; } if (aio->open(dev.device, playback, capture, blocking, channels, format, sample_rate, short_latency)) { return aio; } else { string msg("Open audio device failed"); log_file->write_report(msg, "t_audio_io::open", LOG_NORMAL, LOG_CRITICAL); MEMMAN_DELETE(aio); delete aio; return 0L; } } bool t_audio_io::validate(const t_audio_device& dev, bool playback, bool capture) { t_audio_io *aio = open(dev, playback, capture, false, 1, SAMPLEFORMAT_S16, 8000, true); if (aio) { MEMMAN_DELETE(aio); delete aio; return true; } return false; } t_audio_io::~t_audio_io() {} int t_audio_io::get_sample_rate(void) const { return _sample_rate; } bool t_audio_io::open(const string& device, bool playback, bool capture, bool blocking, int channels, t_audio_sampleformat format, int sample_rate, bool short_latency) { _sample_rate = sample_rate; return true; } t_oss_io::t_oss_io() : fd(-1), play_buffersize(0), rec_buffersize(0) { } t_oss_io::~t_oss_io() { if (fd > 0) { int arg = 0; ioctl(fd, SNDCTL_DSP_RESET, &arg); close(fd); } fd = -1; } bool t_oss_io::open(const string& device, bool playback, bool capture, bool blocking, int channels, t_audio_sampleformat format, int sample_rate, bool short_latency) { t_audio_io::open(device, playback, capture, blocking, channels, format, sample_rate, short_latency); int mode = 0; int status; log_file->write_header("t_oss_io::open", LOG_NORMAL); log_file->write_raw("Opening OSS device: "); log_file->write_raw(device); log_file->write_endl(); if (playback) log_file->write_raw("play\n"); if (capture) log_file->write_raw("capture\n"); log_file->write_footer(); assert (playback || capture); if (playback && capture) mode |= O_RDWR; else if (playback) mode |= O_WRONLY; else if (capture) mode |= O_RDONLY; // On some systems opening the audio devices blocks if another // process or thread has opened it already. To prevent a deadlock // first try to open the device in non-blocking mode. // If the device is still open by another twinkle thread then that // is a bug, but this way at least non deadlock is caused. if(blocking) { fd = ::open(device.c_str(), mode | O_NONBLOCK); if (fd == -1) { string msg("OSS audio device open failed: "); msg += get_error_str(errno); log_file->write_report(msg, "t_oss_io::open", LOG_NORMAL, LOG_CRITICAL); return false; } else { close(fd); fd = -1; } } else mode |= O_NONBLOCK; fd = ::open(device.c_str(), mode); if (fd < 0) { string msg("OSS audio device open failed: "); msg += get_error_str(errno); log_file->write_report(msg, "t_oss_io::open", LOG_NORMAL, LOG_CRITICAL); return false; } // Full duplex if (playback && capture) { status = ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0); if (status == -1) { string msg("SNDCTL_DSP_SETDUPLEX ioctl failed: "); msg += get_error_str(errno); log_file->write_report(msg, "t_oss_io::open", LOG_NORMAL, LOG_CRITICAL); ui->cb_display_msg(TRANSLATE("Sound card cannot be set to full duplex."), MSG_CRITICAL); close(fd); return false; } } // Set fragment size int arg; if (short_latency) { switch (sys_config->get_oss_fragment_size()) { case 16: arg = 0x00ff0004; // 255 buffers of 2^4 bytes each break; case 32: arg = 0x00ff0005; // 255 buffers of 2^5 bytes each break; case 64: arg = 0x00ff0006; // 255 buffers of 2^5 bytes each break; case 128: arg = 0x00ff0007; // 255 buffers of 2^7 bytes each break; case 256: arg = 0x00ff0008; // 255 buffers of 2^8 bytes each break; default: arg = 0x00ff0007; // 255 buffers of 2^7 bytes each } } else { arg = 0x00ff000a; // 255 buffers of 2^10 bytes each } status = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &arg); if (status == -1) { string msg("SNDCTL_DSP_FRAGMENT ioctl failed: "); msg += get_error_str(errno); log_file->write_report(msg, "t_oss_io::open", LOG_NORMAL, LOG_CRITICAL); ui->cb_display_msg(TRANSLATE("Cannot set buffer size on sound card."), MSG_CRITICAL); close(fd); return false; } // Channels arg = channels; status = ioctl(fd, SNDCTL_DSP_CHANNELS, &arg); if (status == -1) { string msg("SNDCTL_DSP_CHANNELS ioctl failed: "); msg += get_error_str(errno); log_file->write_report(msg, "t_oss_io::open", LOG_NORMAL, LOG_CRITICAL); msg = TRANSLATE("Sound card cannot be set to %1 channels."); msg = replace_first(msg, "%1", int2str(channels)); ui->cb_display_msg(msg, MSG_CRITICAL); return false; } if (arg != channels) { log_file->write_report("Unable to set channels", "t_oss_io::open", LOG_NORMAL, LOG_CRITICAL); string msg = "Sound card cannot be set to "; msg = TRANSLATE("Sound card cannot be set to %1 channels."); msg = replace_first(msg, "%1", int2str(channels)); ui->cb_display_msg(msg, MSG_CRITICAL); return false; } // Sample format int fmt; switch (format) { case SAMPLEFORMAT_S16: #ifdef WORDS_BIGENDIAN fmt = AFMT_S16_BE; #else fmt = AFMT_S16_LE; #endif break; case SAMPLEFORMAT_U16: #ifdef WORDS_BIGENDIAN fmt = AFMT_U16_BE; #else fmt = AFMT_U16_LE; #endif break; case SAMPLEFORMAT_S8: fmt = AFMT_S8; break; case SAMPLEFORMAT_U8: fmt = AFMT_U8; break; default: fmt = 0; // fail } arg = fmt; status = ioctl(fd, SNDCTL_DSP_SETFMT, &arg); if (status == -1) { string msg("SNDCTL_DSP_SETFMT ioctl failed: "); msg += get_error_str(errno); log_file->write_report(msg, "t_oss_io::open", LOG_NORMAL, LOG_CRITICAL); ui->cb_display_msg(TRANSLATE("Cannot set sound card to 16 bits recording."), MSG_CRITICAL); return false; } arg = fmt; status = ioctl(fd, SOUND_PCM_WRITE_BITS, &arg); if (status == -1) { string msg("SOUND_PCM_WRITE_BITS ioctl failed: "); msg += get_error_str(errno); log_file->write_report(msg, "t_oss_io::open", LOG_NORMAL, LOG_CRITICAL); ui->cb_display_msg(TRANSLATE("Cannot set sound card to 16 bits playing."), MSG_CRITICAL); return false; } // Sample rate arg = sample_rate; status = ioctl(fd, SNDCTL_DSP_SPEED, &arg); if (status == -1) { string msg("SNDCTL_DSP_SPEED ioctl failed: "); msg += get_error_str(errno); log_file->write_report(msg, "t_oss_io::open", LOG_NORMAL, LOG_CRITICAL); msg = TRANSLATE("Cannot set sound card sample rate to %1"); msg = replace_first(msg, "%1", int2str(sample_rate)); ui->cb_display_msg(msg, MSG_CRITICAL); return false; } play_buffersize = rec_buffersize = 0; if (playback) play_buffersize = get_buffer_space(false); if (capture) rec_buffersize = get_buffer_space(true); return true; } void t_oss_io::enable(bool enable_playback, bool enable_recording) { int arg, status; arg = enable_recording ? PCM_ENABLE_INPUT : 0; arg |= enable_playback ? PCM_ENABLE_OUTPUT : 0; status = ioctl(fd, SNDCTL_DSP_SETTRIGGER, &arg); if (status == -1) { string msg("SNDCTL_DSP_SETTRIGGER ioctl failed: "); msg += get_error_str(errno); log_file->write_report(msg, "t_oss_io::enable", LOG_NORMAL, LOG_CRITICAL); } } void t_oss_io::flush(bool playback_buffer, bool recording_buffer) { for (int i = 0; i < 2; i++) { // i == 0: flush playback buffer, 1: flush recording buffer if (i == 0 && playback_buffer || i == 1 && recording_buffer) { int skip_bytes = ( (i==0) ? play_buffersize : rec_buffersize) - get_buffer_space(i == 1); if(skip_bytes <= 0) continue; unsigned char *trash = new unsigned char[skip_bytes]; MEMMAN_NEW_ARRAY(trash); read(trash, skip_bytes); MEMMAN_DELETE_ARRAY(trash); delete [] trash; } } } int t_oss_io::get_buffer_space(bool is_recording_buffer) { audio_buf_info dsp_info; int status = ioctl(fd, is_recording_buffer ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &dsp_info); if (status == -1) return 0; return dsp_info.bytes; } int t_oss_io::get_buffer_size(bool is_recording_buffer) { if (is_recording_buffer) return rec_buffersize; else return play_buffersize; } bool t_oss_io::play_buffer_underrun(void) { return get_buffer_space(false) >= get_buffer_size(false); } int t_oss_io::read(unsigned char* buf, int len) { return ::read(fd, buf, len); } int t_oss_io::write(const unsigned char* buf, int len) { return ::write(fd, buf, len); } #ifdef HAVE_LIBASOUND t_alsa_io::t_alsa_io() : pcm_play_ptr(0), pcm_rec_ptr(0), play_framesize(1), rec_framesize(1), play_buffersize(0), rec_buffersize(0) { } t_alsa_io::~t_alsa_io() { if (pcm_play_ptr) { log_file->write_header("t_alsa_io::~t_alsa_io", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("snd_pcm_close, handle = "); log_file->write_raw(ptr2str(pcm_play_ptr)); log_file->write_endl(); log_file->write_footer(); // Without the snd_pcm_hw_free, snd_pcm_close sometimes fails. snd_pcm_hw_free(pcm_play_ptr); snd_pcm_close(pcm_play_ptr); pcm_play_ptr = 0; } if (pcm_rec_ptr) { log_file->write_header("t_alsa_io::~t_alsa_io", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("snd_pcm_close, handle = "); log_file->write_raw(ptr2str(pcm_rec_ptr)); log_file->write_endl(); log_file->write_footer(); snd_pcm_hw_free(pcm_rec_ptr); snd_pcm_close(pcm_rec_ptr); pcm_rec_ptr = 0; } } bool t_alsa_io::open(const string& device, bool playback, bool capture, bool blocking, int channels, t_audio_sampleformat format, int sample_rate, bool short_latency) { t_audio_io::open(device, playback, capture, blocking, channels, format, sample_rate, short_latency); int mode = 0; string msg; this->short_latency = short_latency; const char* dev = device.c_str(); if(dev[0] == 0) dev = "default"; log_file->write_header("t_alsa_io::open", LOG_NORMAL); log_file->write_raw("Opening ALSA device: "); log_file->write_raw(dev); log_file->write_endl(); if (playback) log_file->write_raw("play\n"); if (capture) log_file->write_raw("capture\n"); log_file->write_footer(); if (playback && capture) { // Full duplex: Perform the operation in two steps if (!open(device, true, false, blocking, channels, format, sample_rate, short_latency)) return false; if (!open(device, false, true, blocking, channels, format, sample_rate, short_latency)) return false; return true; } snd_pcm_t* pcm_ptr; #define HANDLE_ALSA_ERROR(func) string msg(func); msg += " failed: "; msg += snd_strerror(err); \ log_file->write_report(msg, "t_alsa_io::open", LOG_NORMAL, LOG_CRITICAL); msg = TRANSLATE("Opening ALSA driver failed") + ": " + msg; \ ui->cb_display_msg(msg, MSG_CRITICAL); if(pcm_ptr) snd_pcm_close(pcm_ptr); return false; if (!blocking) mode = SND_PCM_NONBLOCK; int err = snd_pcm_open(&pcm_ptr, dev, playback ? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE, mode); if (err < 0) { string msg("snd_pcm_open failed: "); msg += snd_strerror(err); log_file->write_report(msg, "t_alsa_io::open", LOG_NORMAL, LOG_CRITICAL); msg = "Cannot open ALSA driver for PCM "; if (playback) { msg = TRANSLATE("Cannot open ALSA driver for PCM playback"); } else { msg = TRANSLATE("Cannot open ALSA driver for PCM capture"); } msg += ": "; msg += snd_strerror(err); ui->cb_display_msg(msg, MSG_CRITICAL); return false; } log_file->write_header("t_alsa_io::open", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("snd_pcm_open succeeded, handle = "); log_file->write_raw(ptr2str(pcm_ptr)); log_file->write_endl(); log_file->write_footer(); snd_pcm_hw_params_t *hw_params; snd_pcm_sw_params_t *sw_params; // Set HW params if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) { HANDLE_ALSA_ERROR("snd_pcm_hw_params_malloc"); } MEMMAN_NEW(hw_params); if ((err = snd_pcm_hw_params_any (pcm_ptr, hw_params)) < 0) { HANDLE_ALSA_ERROR("snd_pcm_hw_params_any"); } if ((err = snd_pcm_hw_params_set_access (pcm_ptr, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { HANDLE_ALSA_ERROR("snd_pcm_hw_params_set_access"); } snd_pcm_format_t fmt; int sample_bits; switch (format) { case SAMPLEFORMAT_S16: #ifdef WORDS_BIGENDIAN fmt = SND_PCM_FORMAT_S16_BE; #else fmt = SND_PCM_FORMAT_S16_LE; #endif sample_bits = 16; break; case SAMPLEFORMAT_U16: #ifdef WORDS_BIGENDIAN fmt = SND_PCM_FORMAT_U16_BE; #else fmt = SND_PCM_FORMAT_U16_LE; #endif sample_bits = 16; break; case SAMPLEFORMAT_S8: fmt = SND_PCM_FORMAT_S8; sample_bits = 8; break; case SAMPLEFORMAT_U8: fmt = SND_PCM_FORMAT_U8; sample_bits = 8; break; default: {HANDLE_ALSA_ERROR("invalid sample format");} } if ((err = snd_pcm_hw_params_set_format (pcm_ptr, hw_params, fmt)) < 0) { string s("snd_pcm_hw_params_set_format("); s += int2str(fmt); s += ")"; HANDLE_ALSA_ERROR(s); } // Some sound cards do not support the exact sample rate specified in the // wav files. Still we first try to set the exact sample rate instead of // specifying the 3rd parameter as -1 to choose an approximate. // For some reason on my first sound card that supports the exact rate, // I get mono sound when the -1 parameter is specified. if ((err = snd_pcm_hw_params_set_rate (pcm_ptr, hw_params, sample_rate, 0)) < 0) { msg = "Cannot set soundcard to exact sample rate of "; msg += int2str(sample_rate); msg += ".\nThe card will choose a lower approximate rate."; log_file->write_report(msg, "t_alsa_io::open", LOG_NORMAL, LOG_WARNING); if ((err = snd_pcm_hw_params_set_rate (pcm_ptr, hw_params, sample_rate, -1)) < 0) { string s("snd_pcm_hw_params_set_rate("); s += int2str(sample_rate); s += ")"; HANDLE_ALSA_ERROR(s); } } // Read back card rate for reporting in the log file. unsigned int card_rate; int card_dir; snd_pcm_hw_params_get_rate(hw_params, &card_rate, &card_dir); if ((err = snd_pcm_hw_params_set_channels (pcm_ptr, hw_params, channels)) < 0) { string s("snd_pcm_hw_params_set_channels("); s += int2str(channels); s += ")"; HANDLE_ALSA_ERROR(s); } // Note: The buffersize is in FRAMES, not BYTES (one frame = sizeof(sample) * channels) snd_pcm_uframes_t buffersize; unsigned int periods = 8; // Double buffering int dir = 1; // Set the size of one period in samples if (short_latency) { if (playback) { buffersize = sys_config->get_alsa_play_period_size(); } else { buffersize = sys_config->get_alsa_capture_period_size(); } } else { buffersize = 1024; } if ((err = snd_pcm_hw_params_set_period_size_near (pcm_ptr, hw_params, &buffersize, &dir)) < 0) { HANDLE_ALSA_ERROR("snd_pcm_hw_params_set_period_size_near"); } // The number of periods determines the ALSA application buffer size. // This size must be larger than the jitter buffer. // TODO: use some more sophisticated algorithm here: read back the period // size and calculate the number of periods needed (only in the // short latency case)? if (buffersize <= 64) { periods *= 8; } else if (buffersize <= 256) { periods *= 4; } dir = 1; if ((err = snd_pcm_hw_params_set_periods(pcm_ptr, hw_params, periods, dir)) < 0) { if ((err = snd_pcm_hw_params_set_periods_near(pcm_ptr, hw_params, &periods, &dir)) < 0) { HANDLE_ALSA_ERROR("snd_pcm_hw_params_set_periods"); } } if ((err = snd_pcm_hw_params (pcm_ptr, hw_params)) < 0) { HANDLE_ALSA_ERROR("snd_pcm_hw_params"); } // Find out if the sound card supports pause functionality can_pause = (snd_pcm_hw_params_can_pause(hw_params) == 1); MEMMAN_DELETE(hw_params); snd_pcm_hw_params_free(hw_params); // Set SW params if ((err = snd_pcm_sw_params_malloc(&sw_params)) < 0) { HANDLE_ALSA_ERROR("snd_pcm_sw_params_malloc"); } MEMMAN_NEW(sw_params); if ((err = snd_pcm_sw_params_current (pcm_ptr, sw_params)) < 0) { HANDLE_ALSA_ERROR("snd_pcm_sw_params_current"); } if ((err = snd_pcm_sw_params (pcm_ptr, sw_params)) < 0) { HANDLE_ALSA_ERROR("snd_pcm_sw_params"); } MEMMAN_DELETE(sw_params); snd_pcm_sw_params_free(sw_params); if ((err = snd_pcm_prepare (pcm_ptr)) < 0) { HANDLE_ALSA_ERROR("snd_pcm_prepare"); } if (playback) { pcm_play_ptr = pcm_ptr; play_framesize = (sample_bits / 8) * channels; play_buffersize = (int)buffersize * periods * play_framesize; play_periods = periods; log_file->write_header("t_alsa_io::open", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("ALSA playback buffer settings.\n"); log_file->write_raw("Rate = "); log_file->write_raw(card_rate); log_file->write_raw(" frames/sec\n"); log_file->write_raw("Frame size = "); log_file->write_raw(play_framesize); log_file->write_raw(" bytes\n"); log_file->write_raw("Periods = "); log_file->write_raw(play_periods); log_file->write_endl(); log_file->write_raw("Period size = "); log_file->write_raw(buffersize * play_framesize); log_file->write_raw(" bytes\n"); log_file->write_raw("Buffer size = "); log_file->write_raw(play_buffersize); log_file->write_raw(" bytes\n"); log_file->write_raw("Can pause: "); log_file->write_bool(can_pause); log_file->write_endl(); log_file->write_footer(); } else { // Since audio_rx checks buffer before reading, start manually if ((err = snd_pcm_start(pcm_ptr)) < 0) { HANDLE_ALSA_ERROR("snd_pcm_start"); } pcm_rec_ptr = pcm_ptr; rec_framesize = (sample_bits / 8) * channels; rec_buffersize = (int)buffersize * periods * rec_framesize; rec_periods = periods; // HACK: snd_pcm_delay seems not to work for the dsnoop device. // This code determines if snd_pcm is working. As the capture // device just started, it should return zero or a small number. // So if it returns that more than half of the buffer is filled // already, it seems broken. rec_delay_broken = false; if (get_buffer_space(true) > rec_buffersize / 2) { rec_delay_broken = true; log_file->write_report( "snd_pcm_delay does not work for capture buffer.", "t_alsa_io::open", LOG_NORMAL, LOG_DEBUG); } log_file->write_header("t_alsa_io::open", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("ALSA capture buffer settings.\n"); log_file->write_raw("Rate = "); log_file->write_raw(card_rate); log_file->write_raw(" frames/sec\n"); log_file->write_raw("Frame size = "); log_file->write_raw(rec_framesize); log_file->write_raw(" bytes\n"); log_file->write_raw("Periods = "); log_file->write_raw(rec_periods); log_file->write_endl(); log_file->write_raw("Period size = "); log_file->write_raw(buffersize * rec_framesize); log_file->write_raw(" bytes\n"); log_file->write_raw("Buffer size = "); log_file->write_raw(rec_buffersize); log_file->write_raw(" bytes\n"); log_file->write_raw("Can pause: "); log_file->write_bool(can_pause); log_file->write_endl(); log_file->write_footer(); } return true; #undef HANDLE_ALSA_ERROR } void t_alsa_io::enable(bool enable_playback, bool enable_recording) { if (!can_pause) return; if (pcm_play_ptr) { snd_pcm_pause(pcm_play_ptr, (int)enable_playback); } if (pcm_rec_ptr) { snd_pcm_pause(pcm_rec_ptr, (int)enable_recording); } } void t_alsa_io::flush(bool playback_buffer, bool recording_buffer) { if (playback_buffer && pcm_play_ptr) { // snd_pcm_reset(pcm_play_ptr); snd_pcm_drop(pcm_play_ptr); snd_pcm_prepare(pcm_play_ptr); snd_pcm_start(pcm_play_ptr); } if (recording_buffer && pcm_rec_ptr) { // For some obscure reason snd_pcm_reset causes the CPU // load to rise to 99.9% when playing and capturing is // done on the same sound card. // Therefor snd_pcm_reset is replaced by functions to // stop the card, drop samples and start again. // snd_pcm_reset(pcm_rec_ptr); snd_pcm_drop(pcm_rec_ptr); snd_pcm_prepare(pcm_rec_ptr); snd_pcm_start(pcm_rec_ptr); } } int t_alsa_io::get_buffer_space(bool is_recording_buffer) { int rv; int err; snd_pcm_sframes_t delay; snd_pcm_status_t* status; snd_pcm_status_alloca(&status); if(is_recording_buffer) { if(!pcm_rec_ptr) return 0; // This does not work as snd_pcm_status_get_avail_max does not return // accurate results. // snd_pcm_status(pcm_rec_ptr, status); // rv = rec_framesize * snd_pcm_status_get_avail_max(status); snd_pcm_hwsync(pcm_rec_ptr); if ((err = snd_pcm_delay(pcm_rec_ptr, &delay)) < 0) { string msg = "snd_pcm_delay for capture buffer failed: "; msg += snd_strerror(err); log_file->write_report(msg, "t_alsa_io::get_buffer_space", LOG_NORMAL, LOG_DEBUG); delay = 0; snd_pcm_prepare(pcm_rec_ptr); } if (rec_delay_broken) { rv = 0; // there is no way to get an accurate number } else { rv = int(delay * rec_framesize); } if (rv > rec_buffersize) { rv = rec_buffersize; // capture buffer overrun snd_pcm_prepare(pcm_rec_ptr); } } else { if(!pcm_play_ptr) return 0; snd_pcm_status(pcm_play_ptr, status); rv = play_framesize * snd_pcm_status_get_avail_max(status); if (rv > play_buffersize) { rv = play_buffersize; // playback buffer underrun snd_pcm_prepare(pcm_play_ptr); } } return rv; } int t_alsa_io::get_buffer_size(bool is_recording_buffer) { if (is_recording_buffer) return rec_buffersize; else return play_buffersize; } bool t_alsa_io::play_buffer_underrun(void) { if (!pcm_play_ptr) return false; return snd_pcm_state(pcm_play_ptr) == SND_PCM_STATE_XRUN; } int t_alsa_io::read(unsigned char* buf, int len) { string msg; if (!pcm_rec_ptr) { log_file->write_report("Illegal pcm_rec_prt.", "t_alsa_io::read", LOG_NORMAL, LOG_CRITICAL); return -1; } int len_frames = len / rec_framesize; int read_frames = 0; for(;;) { int read = snd_pcm_readi(pcm_rec_ptr, buf, len_frames); if (read == -EPIPE) { msg = "Capture buffer overrun."; log_file->write_report(msg, "t_alsa_io::read", LOG_NORMAL, LOG_DEBUG); snd_pcm_prepare(pcm_rec_ptr); snd_pcm_start(pcm_rec_ptr); continue; } else if (read <= 0) { msg = "PCM read error: "; msg += snd_strerror(read); log_file->write_report(msg, "t_alsa_io::read", LOG_NORMAL, LOG_DEBUG); return -1; } else if (read < len_frames) { buf += rec_framesize * read; len_frames -= read; read_frames += read; continue; } return (read_frames + read) * rec_framesize; } } int t_alsa_io::write(const unsigned char* buf, int len) { int len_frames = len / play_framesize; int frames_written = 0; string msg; for (;;) { if(!pcm_play_ptr) return -1; int written = snd_pcm_writei(pcm_play_ptr, buf, len_frames); if (written == -EPIPE) { msg = "Playback buffer underrun."; log_file->write_report(msg, "t_alsa_io::write", LOG_NORMAL, LOG_DEBUG); snd_pcm_prepare(pcm_play_ptr); continue; } else if (written == -EINVAL) { msg = "Invalid argument passed to snd_pcm_writei: "; msg += snd_strerror(written); log_file->write_report(msg, "t_alsa_io::write", LOG_NORMAL, LOG_DEBUG); } else if (written < 0) { msg = "PCM write error: "; msg += snd_strerror(written); log_file->write_report(msg, "t_alsa_io::write", LOG_NORMAL, LOG_DEBUG); return -1; } else if (written < len_frames) { buf += written * play_framesize; len_frames -= written; frames_written += written; continue; } return (frames_written + written) * play_framesize; } } // This function fills the specified list with ALSA hardware soundcards found on the system. // It uses plughw:xx instead of hw:xx for specifiers, because hw:xx are not practical to // use (e.g. they require a resampler/channel mixer in the application). // playback indicates if a list with playback or capture devices should be created. void alsa_fill_soundcards(list& l, bool playback) { int err = 0; int card = -1, device = -1; snd_ctl_t *handle; snd_ctl_card_info_t *info; snd_pcm_info_t *pcminfo; snd_ctl_card_info_alloca(&info); snd_pcm_info_alloca(&pcminfo); for (;;) { err = snd_card_next(&card); if (err < 0 || card < 0) break; if (card >= 0) { string name = "hw:"; name += int2str(card); if ((err = snd_ctl_open(&handle, name.c_str(), 0)) < 0) continue; if ((err = snd_ctl_card_info(handle, info)) < 0) { snd_ctl_close(handle); continue; } const char *card_name = snd_ctl_card_info_get_name(info); for (;;) { err = snd_ctl_pcm_next_device(handle, &device); if (err < 0 || device < 0) break; snd_pcm_info_set_device(pcminfo, device); snd_pcm_info_set_subdevice(pcminfo, 0); if (playback) { snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK); } else { snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_CAPTURE); } if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) continue; t_audio_device dev; dev.device = string("plughw:") + int2str(card) + string(",") + int2str(device); dev.name = string(card_name) + " ("; dev.name += snd_pcm_info_get_name(pcminfo); dev.name += ")"; dev.type = t_audio_device::ALSA; l.push_back(dev); } snd_ctl_close(handle); } } } // endif ifdef HAVE_LIBASOUND #endif twinkle-1.10.1/src/audio/audio_device.h000066400000000000000000000076511277565361200177570ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _AUDIO_DEVICE_H #define _AUDIO_DEVICE_H #include #include "twinkle_config.h" using namespace std; #ifndef _SYS_SETTINGS_H class t_audio_device; #endif enum t_audio_sampleformat { SAMPLEFORMAT_U8, SAMPLEFORMAT_S8, SAMPLEFORMAT_S16, SAMPLEFORMAT_U16 }; class t_audio_io { public: virtual ~t_audio_io(); virtual void enable(bool enable_playback, bool enable_recording) = 0; virtual void flush(bool playback_buffer, bool recording_buffer) = 0; // Returns the number of bytes that can be written/read without blocking virtual int get_buffer_space(bool is_recording_buffer) = 0; // Returns the size of the hardware buffer virtual int get_buffer_size(bool is_recording_buffer) = 0; /** Check if a play buffer underrun occurred. */ virtual bool play_buffer_underrun(void) = 0; virtual int read(unsigned char* buf, int len) = 0; virtual int write(const unsigned char* buf, int len) = 0; virtual int get_sample_rate(void) const; static t_audio_io* open(const t_audio_device& dev, bool playback, bool capture, bool blocking, int channels, t_audio_sampleformat format, int sample_rate, bool short_latency); // Validate if an audio device can be opened. static bool validate(const t_audio_device& dev, bool playback, bool capture); protected: virtual bool open(const string& device, bool playback, bool capture, bool blocking, int channels, t_audio_sampleformat format, int sample_rate, bool short_latency); private: int _sample_rate; }; class t_oss_io : public t_audio_io { public: t_oss_io(); virtual ~t_oss_io(); void enable(bool enable_playback, bool enable_recording); void flush(bool playback_buffer, bool recording_buffer); int get_buffer_space(bool is_recording_buffer); int get_buffer_size(bool is_recording_buffer); bool play_buffer_underrun(void); int read(unsigned char* buf, int len); int write(const unsigned char* buf, int len); protected: bool open(const string& device, bool playback, bool capture, bool blocking, int channels, t_audio_sampleformat format, int sample_rate, bool short_latency); private: int fd; int play_buffersize, rec_buffersize; }; #ifdef HAVE_LIBASOUND class t_alsa_io : public t_audio_io { public: t_alsa_io(); virtual ~t_alsa_io(); void enable(bool enable_playback, bool enable_recording); void flush(bool playback_buffer, bool recording_buffer); int get_buffer_space(bool is_recording_buffer); int get_buffer_size(bool is_recording_buffer); bool play_buffer_underrun(void); int read(unsigned char* buf, int len); int write(const unsigned char* buf, int len); protected: bool open(const string& device, bool playback, bool capture, bool blocking, int channels, t_audio_sampleformat format, int sample_rate, bool short_latency); private: struct _snd_pcm *pcm_play_ptr, *pcm_rec_ptr; int play_framesize, rec_framesize; int play_buffersize, rec_buffersize; int play_periods, rec_periods; bool short_latency; // snd_pcm_delay should return the number of bytes in the buffer. // For some reason however, if the capture device is a software mixer, // it returns inaccurate values. // This flag if the functionality is broken. bool rec_delay_broken; // Indicates if snd_pcm_pause works for this device bool can_pause; }; #endif #endif twinkle-1.10.1/src/audio/audio_encoder.cpp000066400000000000000000000272551277565361200204740ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include "audio_encoder.h" #ifdef HAVE_ILBC #ifndef HAVE_ILBC_CPP extern "C" { #endif #include #ifndef HAVE_ILBC_CPP } #endif #endif ////////////////////////////////////////// // class t_audio_encoder ////////////////////////////////////////// t_audio_encoder::t_audio_encoder(uint16 payload_id, uint16 ptime, t_user *user_config) : _payload_id(payload_id), _ptime(ptime), _user_config(user_config) {} t_audio_codec t_audio_encoder::get_codec(void) const { return _codec; } uint16 t_audio_encoder::get_payload_id(void) const { return _payload_id; } uint16 t_audio_encoder::get_ptime(void) const { return _ptime; } uint16 t_audio_encoder::get_sample_rate(void) const { return audio_sample_rate(_codec); } uint16 t_audio_encoder::get_max_payload_size(void) const { return _max_payload_size; } ////////////////////////////////////////// // class t_g711a_audio_encoder ////////////////////////////////////////// t_g711a_audio_encoder::t_g711a_audio_encoder(uint16 payload_id, uint16 ptime, t_user *user_config) : t_audio_encoder(payload_id, ptime, user_config) { _codec = CODEC_G711_ALAW; if (ptime == 0) _ptime = PTIME_G711_ALAW; _max_payload_size = audio_sample_rate(_codec)/1000 * _ptime; } uint16 t_g711a_audio_encoder::encode(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size, bool &silence) { assert(nsamples <= payload_size); silence = false; for (int i = 0; i < nsamples; i++) { payload[i] = linear2alaw(sample_buf[i]); } return nsamples; } ////////////////////////////////////////// // class t_g711u_audio_encoder ////////////////////////////////////////// t_g711u_audio_encoder::t_g711u_audio_encoder(uint16 payload_id, uint16 ptime, t_user *user_config) : t_audio_encoder(payload_id, ptime, user_config) { _codec = CODEC_G711_ULAW; if (ptime == 0) _ptime = PTIME_G711_ULAW; _max_payload_size = audio_sample_rate(_codec)/1000 * _ptime; } uint16 t_g711u_audio_encoder::encode(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size, bool &silence) { assert(nsamples <= payload_size); silence = false; for (int i = 0; i < nsamples; i++) { payload[i] = linear2ulaw(sample_buf[i]); } return nsamples; } ////////////////////////////////////////// // class t_gsm_audio_encoder ////////////////////////////////////////// t_gsm_audio_encoder::t_gsm_audio_encoder(uint16 payload_id, uint16 ptime, t_user *user_config) : t_audio_encoder(payload_id, PTIME_GSM, user_config) { _codec = CODEC_GSM; _max_payload_size = 33; gsm_encoder = gsm_create(); } t_gsm_audio_encoder::~t_gsm_audio_encoder() { gsm_destroy(gsm_encoder); } uint16 t_gsm_audio_encoder::encode(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size, bool &silence) { assert(payload_size >= _max_payload_size); silence = false; gsm_encode(gsm_encoder, sample_buf, payload); return _max_payload_size; } #ifdef HAVE_SPEEX ////////////////////////////////////////// // class t_speex_audio_encoder ////////////////////////////////////////// t_speex_audio_encoder::t_speex_audio_encoder(uint16 payload_id, uint16 ptime, t_mode mode, t_user *user_config) : t_audio_encoder(payload_id, PTIME_SPEEX, user_config) { speex_bits_init(&speex_bits); _mode = mode; switch (mode) { case MODE_NB: _codec = CODEC_SPEEX_NB; speex_enc_state = speex_encoder_init(&speex_nb_mode); break; case MODE_WB: _codec = CODEC_SPEEX_WB; speex_enc_state = speex_encoder_init(&speex_wb_mode); break; case MODE_UWB: _codec = CODEC_SPEEX_UWB; speex_enc_state = speex_encoder_init(&speex_uwb_mode); break; default: assert(false); } int arg; // Bit rate type switch (_user_config->get_speex_bit_rate_type()) { case BIT_RATE_CBR: arg = 0; speex_encoder_ctl(speex_enc_state, SPEEX_SET_VBR, &arg); break; case BIT_RATE_VBR: arg = 1; speex_encoder_ctl(speex_enc_state, SPEEX_SET_VBR, &arg); break; case BIT_RATE_ABR: if (_codec == CODEC_SPEEX_NB) { arg = user_config->get_speex_abr_nb(); speex_encoder_ctl(speex_enc_state, SPEEX_SET_ABR, &arg); } else { arg = user_config->get_speex_abr_wb(); speex_encoder_ctl(speex_enc_state, SPEEX_SET_ABR, &arg); } break; default: assert(false); } _max_payload_size = 1500; /*** ENCODER OPTIONS ***/ // Discontinuos trasmission arg = (_user_config->get_speex_dtx() ? 1 : 0); speex_encoder_ctl(speex_enc_state, SPEEX_SET_DTX, &arg); // Quality arg = _user_config->get_speex_quality(); if (_user_config->get_speex_bit_rate_type() == BIT_RATE_VBR) speex_encoder_ctl(speex_enc_state, SPEEX_SET_VBR_QUALITY, &arg); else speex_encoder_ctl(speex_enc_state, SPEEX_SET_QUALITY, &arg); // Complexity arg = _user_config->get_speex_complexity(); speex_encoder_ctl(speex_enc_state, SPEEX_SET_COMPLEXITY, &arg); } t_speex_audio_encoder::~t_speex_audio_encoder() { speex_bits_destroy(&speex_bits); speex_encoder_destroy(speex_enc_state); } uint16 t_speex_audio_encoder::encode(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size, bool &silence) { assert(payload_size >= _max_payload_size); silence = false; speex_bits_reset(&speex_bits); if (speex_encode_int(speex_enc_state, sample_buf, &speex_bits) == 0) silence = true; return speex_bits_write(&speex_bits, (char *)payload, payload_size); } #endif #ifdef HAVE_ILBC ////////////////////////////////////////// // class t_ilbc_audio_encoder ////////////////////////////////////////// t_ilbc_audio_encoder::t_ilbc_audio_encoder(uint16 payload_id, uint16 ptime, t_user *user_config) : t_audio_encoder(payload_id, (ptime < 25 ? 20 : 30), user_config) { _codec = CODEC_ILBC; _mode = _ptime; if (_mode == 20) { _max_payload_size = NO_OF_BYTES_20MS; } else { _max_payload_size = NO_OF_BYTES_30MS; } initEncode(&_ilbc_encoder, _mode); } uint16 t_ilbc_audio_encoder::encode(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size, bool &silence) { assert(payload_size >= _max_payload_size); assert(nsamples == _ilbc_encoder.blockl); silence = false; float block[nsamples]; for (int i = 0; i < nsamples; i++) { block[i] = static_cast(sample_buf[i]); } iLBC_encode((unsigned char*)payload, block, &_ilbc_encoder); return _ilbc_encoder.no_of_bytes; } #endif ////////////////////////////////////////// // class t_g726_encoder ////////////////////////////////////////// t_g726_audio_encoder::t_g726_audio_encoder(uint16 payload_id, uint16 ptime, t_bit_rate bit_rate, t_user *user_config) : t_audio_encoder(payload_id, ptime, user_config) { _bit_rate = bit_rate; switch (bit_rate) { case BIT_RATE_16: _codec = CODEC_G726_16; break; case BIT_RATE_24: _codec = CODEC_G726_24; break; case BIT_RATE_32: _codec = CODEC_G726_32; break; case BIT_RATE_40: _codec = CODEC_G726_40; break; default: assert(false); } if (ptime == 0) _ptime = PTIME_G726; _max_payload_size = audio_sample_rate(_codec)/1000 * _ptime; _packing = user_config->get_g726_packing(); g72x_init_state(&_state); } uint16 t_g726_audio_encoder::encode_16(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size) { assert(nsamples % 4 == 0); assert(nsamples / 4 <= payload_size); for (int i = 0; i < nsamples; i += 4) { payload[i >> 2] = 0; for (int j = 0; j < 4; j++) { uint8 v = static_cast(g723_16_encoder(sample_buf[i+j], AUDIO_ENCODING_LINEAR, &_state)); if (_packing == G726_PACK_RFC3551) { payload[i >> 2] |= v << (j * 2); } else { payload[i >> 2] |= v << ((3-j) * 2); } } } return nsamples >> 2; } uint16 t_g726_audio_encoder::encode_24(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size) { assert(nsamples % 8 == 0); assert(nsamples / 8 * 3 <= payload_size); for (int i = 0; i < nsamples; i += 8) { uint32 v = 0; for (int j = 0; j < 8; j++) { if (_packing == G726_PACK_RFC3551) { v |= static_cast(g723_24_encoder(sample_buf[i+j], AUDIO_ENCODING_LINEAR, &_state)) << (j * 3); } else { v |= static_cast(g723_24_encoder(sample_buf[i+j], AUDIO_ENCODING_LINEAR, &_state)) << ((7-j) * 3); } } payload[(i >> 3) * 3] = static_cast(v & 0xff); payload[(i >> 3) * 3 + 1] = static_cast((v >> 8) & 0xff); payload[(i >> 3) * 3 + 2] = static_cast((v >> 16) & 0xff); } return (nsamples >> 3) * 3; } uint16 t_g726_audio_encoder::encode_32(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size) { assert(nsamples % 2 == 0); assert(nsamples / 2 <= payload_size); for (int i = 0; i < nsamples; i += 2) { payload[i >> 1] = 0; for (int j = 0; j < 2; j++) { uint8 v = static_cast(g721_encoder(sample_buf[i+j], AUDIO_ENCODING_LINEAR, &_state)); if (_packing == G726_PACK_RFC3551) { payload[i >> 1] |= v << (j * 4); } else { payload[i >> 1] |= v << ((1-j) * 4); } } } return nsamples >> 1; } uint16 t_g726_audio_encoder::encode_40(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size) { assert(nsamples % 8 == 0); assert(nsamples / 8 * 5 <= payload_size); for (int i = 0; i < nsamples; i += 8) { uint64 v = 0; for (int j = 0; j < 8; j++) { if (_packing == G726_PACK_RFC3551) { v |= static_cast(g723_40_encoder(sample_buf[i+j], AUDIO_ENCODING_LINEAR, &_state)) << (j * 5); } else { v |= static_cast(g723_40_encoder(sample_buf[i+j], AUDIO_ENCODING_LINEAR, &_state)) << ((7-j) * 5); } } payload[(i >> 3) * 5] = static_cast(v & 0xff); payload[(i >> 3) * 5 + 1] = static_cast((v >> 8) & 0xff); payload[(i >> 3) * 5 + 2] = static_cast((v >> 16) & 0xff); payload[(i >> 3) * 5 + 3] = static_cast((v >> 24) & 0xff); payload[(i >> 3) * 5 + 4] = static_cast((v >> 32) & 0xff); } return (nsamples >> 3) * 5; } uint16 t_g726_audio_encoder::encode(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size, bool &silence) { silence = false; switch (_bit_rate) { case BIT_RATE_16: return encode_16(sample_buf, nsamples, payload, payload_size); case BIT_RATE_24: return encode_24(sample_buf, nsamples, payload, payload_size); case BIT_RATE_32: return encode_32(sample_buf, nsamples, payload, payload_size); case BIT_RATE_40: return encode_40(sample_buf, nsamples, payload, payload_size); default: assert(false); } return 0; } #ifdef HAVE_BCG729 t_g729a_audio_encoder::t_g729a_audio_encoder(uint16 payload_id, uint16 ptime, t_user *user_config) : t_audio_encoder(payload_id, ptime, user_config) { _context = initBcg729EncoderChannel(); } t_g729a_audio_encoder::~t_g729a_audio_encoder() { closeBcg729EncoderChannel(_context); } uint16 t_g729a_audio_encoder::encode(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size, bool &silence) { assert ((nsamples % 80) == 0); assert (payload_size >= (nsamples/8)); silence = false; for (uint16 done = 0; done < nsamples; done += 80) { bcg729Encoder(_context, &sample_buf[done], &payload[done / 8]); } return nsamples / 8; } #endif twinkle-1.10.1/src/audio/audio_encoder.h000066400000000000000000000120401277565361200201230ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Audio encoders #ifndef _AUDIO_ENCODER_H #define _AUDIO_ENCODER_H #include "twinkle_config.h" #include "audio_codecs.h" #include "user.h" #ifdef HAVE_GSM extern "C" { #include } #else #include "gsm/inc/gsm.h" #endif #ifdef HAVE_SPEEX #include #endif #ifdef HAVE_BCG729 extern "C" { # include } #endif #ifdef HAVE_ILBC #ifndef HAVE_ILBC_CPP extern "C" { #endif #include #ifndef HAVE_ILBC_CPP } #endif #endif // Abstract definition of an audio encoder class t_audio_encoder { protected: t_audio_codec _codec; uint16 _payload_id; // payload id for the codec uint16 _ptime; // in milliseconds uint16 _max_payload_size; // maximum size of payload t_user *_user_config; t_audio_encoder(uint16 payload_id, uint16 ptime, t_user *user_config); public: virtual ~t_audio_encoder() {}; t_audio_codec get_codec(void) const; uint16 get_payload_id(void) const; uint16 get_ptime(void) const; uint16 get_sample_rate(void) const; uint16 get_max_payload_size(void) const; // Encode a 16-bit PCM sample buffer to a encoded payload // Returns the number of bytes written into the payload. // The silence flag indicates if the returned sound samples represent silence // that may be suppressed. virtual uint16 encode(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size, bool &silence) = 0; }; // G.711 A-law class t_g711a_audio_encoder : public t_audio_encoder { public: t_g711a_audio_encoder(uint16 payload_id, uint16 ptime, t_user *user_config); virtual uint16 encode(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size, bool &silence); }; // G.711 u-law class t_g711u_audio_encoder : public t_audio_encoder { public: t_g711u_audio_encoder(uint16 payload_id, uint16 ptime, t_user *user_config); virtual uint16 encode(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size, bool &silence); }; // GSM class t_gsm_audio_encoder : public t_audio_encoder { private: gsm gsm_encoder; public: t_gsm_audio_encoder(uint16 payload_id, uint16 ptime, t_user *user_config); virtual ~t_gsm_audio_encoder(); virtual uint16 encode(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size, bool &silence); }; #ifdef HAVE_SPEEX class t_speex_audio_encoder : public t_audio_encoder { public: enum t_mode { MODE_NB, // Narrow band MODE_WB, // Wide band MODE_UWB // Ultra wide band }; private: SpeexBits speex_bits; void *speex_enc_state; t_mode _mode; public: t_speex_audio_encoder(uint16 payload_id, uint16 ptime, t_mode mode, t_user *user_config); virtual ~t_speex_audio_encoder(); virtual uint16 encode(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size, bool &silence); }; #endif #ifdef HAVE_ILBC class t_ilbc_audio_encoder : public t_audio_encoder { private: iLBC_Enc_Inst_t _ilbc_encoder; uint8 _mode; // 20, 30 ms (frame size) public: t_ilbc_audio_encoder(uint16 payload_id, uint16 ptime, t_user *user_config); virtual uint16 encode(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size, bool &silence); }; #endif class t_g726_audio_encoder : public t_audio_encoder { public: enum t_bit_rate { BIT_RATE_16, BIT_RATE_24, BIT_RATE_32, BIT_RATE_40 }; private: uint16 encode_16(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size); uint16 encode_24(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size); uint16 encode_32(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size); uint16 encode_40(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size); g72x_state _state; t_bit_rate _bit_rate; t_g726_packing _packing; public: t_g726_audio_encoder(uint16 payload_id, uint16 ptime, t_bit_rate bit_rate, t_user *user_config); virtual uint16 encode(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size, bool &silence); }; #ifdef HAVE_BCG729 class t_g729a_audio_encoder : public t_audio_encoder { public: t_g729a_audio_encoder(uint16 payload_id, uint16 ptime, t_user *user_config); virtual ~t_g729a_audio_encoder(); virtual uint16 encode(int16 *sample_buf, uint16 nsamples, uint8 *payload, uint16 payload_size, bool &silence); private: bcg729EncoderChannelContextStruct* _context; }; #endif #endif twinkle-1.10.1/src/audio/audio_rx.cpp000066400000000000000000000701031277565361200174740ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include #include #include "audio_rx.h" #include "log.h" #include "phone.h" #include "rtp_telephone_event.h" #include "userintf.h" #include "line.h" #include "sys_settings.h" #include "sequence_number.h" #include "audits/memman.h" extern t_phone *phone; #define SAMPLE_BUF_SIZE (audio_encoder->get_ptime() * audio_encoder->get_sample_rate()/1000 *\ AUDIO_SAMPLE_SIZE/8) // Debug macro to print timestamp #define DEBUG_TS(s) { gettimeofday(&debug_timer, NULL);\ cout << "DEBUG: ";\ cout << debug_timer.tv_sec * 1000 +\ debug_timer.tv_usec / 1000;\ cout << " " << (s) << endl;\ } ////////// // PRIVATE ////////// bool t_audio_rx::get_sound_samples(unsigned short &sound_payload_size, bool &silence) { int status; struct timespec sleeptimer; //struct timeval debug_timer; silence = false; mtx_3way.lock(); if (is_3way && !is_main_rx_3way) { // We are not the main receiver in a 3-way call, so // get the sound samples from the local media buffer. // This buffer will be filled by the main receiver. if (!media_3way_peer_rx->get(input_sample_buf, SAMPLE_BUF_SIZE)) { // The mutex is unlocked before going to sleep. // First I had the mutex unlock after the sleep. // That worked fine with LinuxThreading, but it does // not work with NPTL. It causes a deadlock when // the main receiver calls post_media_peer_rx_3way // as NPTL does not fair scheduling. This thread // simly gets the lock again and the main receiver // dies from starvation. mtx_3way.unlock(); // There is not enough data yet. Sleep for 1 ms. sleeptimer.tv_sec = 0; sleeptimer.tv_nsec = 1000000; nanosleep(&sleeptimer, NULL); return false; } mtx_3way.unlock(); } else { // Don't keep the 3way mutex locked while waiting for the DSP. mtx_3way.unlock(); // Get the sound samples from the DSP status = input_device->read(input_sample_buf, SAMPLE_BUF_SIZE); if (status != SAMPLE_BUF_SIZE) { if (!logged_capture_failure) { // Log this failure only once log_file->write_header("t_audio_rx::get_sound_samples", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Audio rx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": sound capture failed.\n"); log_file->write_raw("Status: "); log_file->write_raw(status); log_file->write_endl(); log_file->write_footer(); logged_capture_failure = true; } stop_running = true; return false; } // If line is muted, then fill sample buffer with silence. // Note that we keep reading the dsp, to prevent the DSP buffers // from filling up. if (get_line()->get_is_muted()) { memset(input_sample_buf, 0, SAMPLE_BUF_SIZE); } } // Convert buffer to a buffer of shorts as the samples are 16 bits short *sb = (short *)input_sample_buf; mtx_3way.lock(); if (is_3way) { // Send the sound samples to the other receiver if we // are the main receiver. // There may be no other receiver when one of the far-ends // has put the call on-hold. if (is_main_rx_3way && peer_rx_3way) { peer_rx_3way->post_media_peer_rx_3way(input_sample_buf, SAMPLE_BUF_SIZE, audio_encoder->get_sample_rate()); } // Mix the sound samples with the 3rd party if (media_3way_peer_tx->get(mix_buf_3way, SAMPLE_BUF_SIZE)) { short *mix_sb = (short *)mix_buf_3way; for (int i = 0; i < SAMPLE_BUF_SIZE / 2; i++) { sb[i] = mix_linear_pcm(sb[i], mix_sb[i]); } } } mtx_3way.unlock(); /*** PREPROCESSING & ENCODING ***/ bool preprocessing_silence = false; #ifdef HAVE_SPEEX // speex acoustic echo cancellation if (audio_session->get_do_echo_cancellation() && !audio_session->get_echo_captured_last()) { spx_int16_t *input_buf = new spx_int16_t[SAMPLE_BUF_SIZE/2]; MEMMAN_NEW_ARRAY(input_buf); for (int i = 0; i < SAMPLE_BUF_SIZE / 2; i++) { input_buf[i] = sb[i]; } speex_echo_capture(audio_session->get_speex_echo_state(), input_buf, sb); audio_session->set_echo_captured_last(true); MEMMAN_DELETE_ARRAY(input_buf); delete [] input_buf; } // preprocessing preprocessing_silence = !speex_preprocess_run(speex_preprocess_state, sb); // According to the speex API documentation the return value // from speex_preprocess_run() is only defined when VAD is // enabled. So to be safe, reset the return value, if VAD is // disabled. if (!speex_dsp_vad) preprocessing_silence = false; #endif // encoding sound_payload_size = audio_encoder->encode(sb, nsamples, payload, payload_size, silence); // recognizing silence (both from preprocessing and encoding) silence = silence || preprocessing_silence; return true; } bool t_audio_rx::get_dtmf_event(void) { // DTMF events are not supported in a 3-way conference if (is_3way) return false; if (!sema_dtmf_q.try_down()) { // No DTMF event available return false; } // Get next DTMF event mtx_dtmf_q.lock(); t_dtmf_event dtmf_event = dtmf_queue.front(); dtmf_queue.pop(); mtx_dtmf_q.unlock(); ui->cb_async_send_dtmf(get_line()->get_line_number(), dtmf_event.dtmf_tone); // Create DTMF player if (dtmf_event.inband) { dtmf_player = new t_inband_dtmf_player(this, audio_encoder, user_config, dtmf_event.dtmf_tone, timestamp, nsamples); MEMMAN_NEW(dtmf_player); // Log DTMF event log_file->write_header("t_audio_rx::get_dtmf_event", LOG_NORMAL); log_file->write_raw("Audio rx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": start inband DTMF tone - "); log_file->write_raw(dtmf_event.dtmf_tone); log_file->write_endl(); log_file->write_footer(); } else { // The telephone events may have a different sampling rate than // the audio codec. Change nsamples accordingly. nsamples = audio_sample_rate(CODEC_TELEPHONE_EVENT)/1000 * audio_encoder->get_ptime(); dtmf_player = new t_rtp_event_dtmf_player(this, audio_encoder, user_config, dtmf_event.dtmf_tone, timestamp, nsamples); MEMMAN_NEW(dtmf_player); // Log DTMF event log_file->write_header("t_audio_rx::get_dtmf_event", LOG_NORMAL); log_file->write_raw("Audio rx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": start DTMF event - "); log_file->write_raw(dtmf_event.dtmf_tone); log_file->write_endl(); log_file->write_raw("Payload type: "); log_file->write_raw(pt_telephone_event); log_file->write_endl(); log_file->write_footer(); // Set RTP payload format // HACK: the sample rate for telephone events is 8000, but the // ccRTP stack does not handle it well when the sample rate // changes. When the sample rate of the audio codec is kept // on the ccRTP session settings, then all works fine. rtp_session->setPayloadFormat(DynamicPayloadFormat(pt_telephone_event, audio_encoder->get_sample_rate())); // should be this: audio_sample_rate(CODEC_TELEPHONE_EVENT) // As all RTP event contain the same timestamp, the ccRTP stack will // discard packets when the timestamp gets to old. // Increase the expire timeout value to prevent this. rtp_session->setExpireTimeout((JITTER_BUF_MS + user_config->get_dtmf_duration() + user_config->get_dtmf_pause()) * 1000); } return true; } void t_audio_rx::set_sound_payload_format(void) { nsamples = audio_encoder->get_sample_rate()/1000 * audio_encoder->get_ptime(); rtp_session->setPayloadFormat(DynamicPayloadFormat(audio_encoder->get_payload_id(), audio_encoder->get_sample_rate())); } ////////// // PUBLIC ////////// t_audio_rx::t_audio_rx(t_audio_session *_audio_session, t_audio_io *_input_device, t_twinkle_rtp_session *_rtp_session, t_audio_codec _codec, unsigned short _payload_id, unsigned short _ptime) : sema_dtmf_q(0) { audio_session = _audio_session; user_config = audio_session->get_line()->get_user(); assert(user_config); input_device = _input_device; rtp_session = _rtp_session; dtmf_player = NULL; is_running = false; stop_running = false; logged_capture_failure = false; use_nat_keepalive = phone->use_nat_keepalive(user_config); pt_telephone_event = -1; // Create audio encoder switch (_codec) { case CODEC_G711_ALAW: audio_encoder = new t_g711a_audio_encoder(_payload_id, _ptime, user_config); MEMMAN_NEW(audio_encoder); break; case CODEC_G711_ULAW: audio_encoder = new t_g711u_audio_encoder(_payload_id, _ptime, user_config); MEMMAN_NEW(audio_encoder); break; case CODEC_GSM: audio_encoder = new t_gsm_audio_encoder(_payload_id, _ptime, user_config); MEMMAN_NEW(audio_encoder); break; #ifdef HAVE_SPEEX case CODEC_SPEEX_NB: audio_encoder = new t_speex_audio_encoder(_payload_id, _ptime, t_speex_audio_encoder::MODE_NB, user_config); MEMMAN_NEW(audio_encoder); break; case CODEC_SPEEX_WB: audio_encoder = new t_speex_audio_encoder(_payload_id, _ptime, t_speex_audio_encoder::MODE_WB, user_config); MEMMAN_NEW(audio_encoder); break; case CODEC_SPEEX_UWB: audio_encoder = new t_speex_audio_encoder(_payload_id, _ptime, t_speex_audio_encoder::MODE_UWB, user_config); MEMMAN_NEW(audio_encoder); break; #endif #ifdef HAVE_ILBC case CODEC_ILBC: audio_encoder = new t_ilbc_audio_encoder(_payload_id, _ptime, user_config); MEMMAN_NEW(audio_encoder); break; #endif case CODEC_G726_16: audio_encoder = new t_g726_audio_encoder(_payload_id, _ptime, t_g726_audio_encoder::BIT_RATE_16, user_config); MEMMAN_NEW(audio_encoder); break; case CODEC_G726_24: audio_encoder = new t_g726_audio_encoder(_payload_id, _ptime, t_g726_audio_encoder::BIT_RATE_24, user_config); MEMMAN_NEW(audio_encoder); break; case CODEC_G726_32: audio_encoder = new t_g726_audio_encoder(_payload_id, _ptime, t_g726_audio_encoder::BIT_RATE_32, user_config); MEMMAN_NEW(audio_encoder); break; case CODEC_G726_40: audio_encoder = new t_g726_audio_encoder(_payload_id, _ptime, t_g726_audio_encoder::BIT_RATE_40, user_config); MEMMAN_NEW(audio_encoder); break; #ifdef HAVE_BCG729 case CODEC_G729A: audio_encoder = new t_g729a_audio_encoder(_payload_id, _ptime, user_config); MEMMAN_NEW(audio_encoder); break; #endif default: assert(false); } payload_size = audio_encoder->get_max_payload_size(); input_sample_buf = new unsigned char[SAMPLE_BUF_SIZE]; MEMMAN_NEW_ARRAY(input_sample_buf); payload = new unsigned char[payload_size]; MEMMAN_NEW_ARRAY(payload); nsamples = audio_encoder->get_sample_rate()/1000 * audio_encoder->get_ptime(); // Initialize 3-way settings to 'null' media_3way_peer_tx = NULL; media_3way_peer_rx = NULL; peer_rx_3way = NULL; mix_buf_3way = NULL; is_3way = false; is_main_rx_3way = false; #ifdef HAVE_SPEEX // initializing speex preprocessing state speex_preprocess_state = speex_preprocess_state_init(nsamples, audio_encoder->get_sample_rate()); int arg; float farg; // Noise reduction arg = (user_config->get_speex_dsp_nrd() ? 1 : 0); speex_preprocess_ctl(speex_preprocess_state, SPEEX_PREPROCESS_SET_DENOISE, &arg); arg = -30; speex_preprocess_ctl(speex_preprocess_state, SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &arg); // Automatic gain control arg = (user_config->get_speex_dsp_agc() ? 1 : 0); speex_preprocess_ctl(speex_preprocess_state, SPEEX_PREPROCESS_SET_AGC, &arg); farg = (float) (user_config->get_speex_dsp_agc_level()) * 327.68f; speex_preprocess_ctl(speex_preprocess_state, SPEEX_PREPROCESS_SET_AGC_LEVEL, &farg); arg = 30; speex_preprocess_ctl(speex_preprocess_state, SPEEX_PREPROCESS_SET_AGC_MAX_GAIN, &arg); // Voice activity detection arg = (user_config->get_speex_dsp_vad() ? 1 : 0); speex_dsp_vad = (bool)arg; speex_preprocess_ctl(speex_preprocess_state, SPEEX_PREPROCESS_SET_VAD, &arg); // Acoustic echo cancellation if (audio_session->get_do_echo_cancellation()) { speex_preprocess_ctl(speex_preprocess_state, SPEEX_PREPROCESS_SET_ECHO_STATE, audio_session->get_speex_echo_state()); } #endif } t_audio_rx::~t_audio_rx() { struct timespec sleeptimer; if (is_running) { stop_running = true; do { sleeptimer.tv_sec = 0; sleeptimer.tv_nsec = 10000000; nanosleep(&sleeptimer, NULL); } while (is_running); } #ifdef HAVE_SPEEX // cleaning speex preprocessing if (audio_session->get_do_echo_cancellation()) { speex_echo_state_reset(audio_session->get_speex_echo_state()); } speex_preprocess_state_destroy(speex_preprocess_state); #endif MEMMAN_DELETE_ARRAY(input_sample_buf); delete [] input_sample_buf; MEMMAN_DELETE_ARRAY(payload); delete [] payload; MEMMAN_DELETE(audio_encoder); delete audio_encoder; // Clean up resources for 3-way conference calls if (media_3way_peer_tx) { MEMMAN_DELETE(media_3way_peer_tx); delete media_3way_peer_tx; } if (media_3way_peer_rx) { MEMMAN_DELETE(media_3way_peer_rx); delete media_3way_peer_rx; } if (mix_buf_3way) { MEMMAN_DELETE_ARRAY(mix_buf_3way); delete [] mix_buf_3way; } if (dtmf_player) { MEMMAN_DELETE(dtmf_player); delete dtmf_player; } } void t_audio_rx::set_running(bool running) { is_running = running; } // NOTE: no operations on the phone object are allowed inside the run() method. // Such an operation needs a lock on the transaction layer. The destructor // on audio_rx is called while this lock is locked. The destructor waits // in a busy loop for the run() method to finish. If the run() method would // need the phone lock, this would lead to a dead lock (and a long trip // in debug hell!) void t_audio_rx::run(void) { //struct timeval debug_timer; unsigned short sound_payload_size; uint32 dtmf_rtp_timestamp; phone->add_prohibited_thread(); ui->add_prohibited_thread(); // This flag indicates if we are currently in a silence period. // The start of a new stream is assumed to start in silence, such // that the very first RTP packet will be marked. bool silence_period = true; uint64 silence_nsamples = 0; // duration in samples // This flag indicates if a sound frame can be suppressed bool suppress_samples = false; // The running flag is set already in t_audio_session::run to prevent // a crash when the thread gets destroyed before it starts running. // is_running = true; // For a 3-way conference only the main receiver has access // to the dsp. if (!is_3way || is_main_rx_3way) { // Enable recording if (sys_config->equal_audio_dev(sys_config->get_dev_speaker(), sys_config->get_dev_mic())) { input_device->enable(true, true); } else { input_device->enable(false, true); } // If the stream is stopped for call-hold, then the buffer might // be filled with old sound samples. input_device->flush(false, true); } // Synchronize the timestamp driven by the sampling rate // of the recording with the timestamp of the RTP session. // As the RTP session is already created in advance, the // RTP clock is a bit ahead already. timestamp = rtp_session->getCurrentTimestamp() + nsamples; // This loop keeps running until the stop_running flag is set to true. // When a call is being released the stop_running flag is set to true. // At that moment the lock on the transaction layer (phone) is taken. // So do not use operations that take the phone lock, otherwise a // dead lock may occur during call release. while (true) { if (stop_running) break; if (dtmf_player) { rtp_session->setMark(false); // Skip samples from sound card input_device->read(input_sample_buf, SAMPLE_BUF_SIZE); sound_payload_size = dtmf_player->get_payload( payload, payload_size, timestamp, dtmf_rtp_timestamp); silence_period = false; } else if (get_dtmf_event()) { // RFC 2833 // Set marker in first RTP packet of a DTMF event rtp_session->setMark(true); // Skip samples from sound card input_device->read(input_sample_buf, SAMPLE_BUF_SIZE); assert(dtmf_player); sound_payload_size = dtmf_player->get_payload( payload, payload_size, timestamp, dtmf_rtp_timestamp); silence_period = false; } else if (get_sound_samples(sound_payload_size, suppress_samples)) { if (suppress_samples && use_nat_keepalive) { if (!silence_period) silence_nsamples = 0; // Send a silence packet at the NAT keep alive interval // to keep the NAT bindings for RTP fresh. silence_nsamples += SAMPLE_BUF_SIZE / 2; if (silence_nsamples > (uint64_t)user_config->get_timer_nat_keepalive() * 1000 * audio_encoder->get_sample_rate()) { suppress_samples = false; } } if (silence_period && !suppress_samples) { // RFC 3551 4.1 // Set marker bit in first RTP packet after silence rtp_session->setMark(true); } else { rtp_session->setMark(false); } silence_period = suppress_samples; } else { continue; } // If timestamp is more than 1 payload size ahead of the clock of // the ccRTP stack, then drop the current payload and do not advance // the timestamp. This will happen if the DSP delivers more // sound samples than the set sample rate. To compensate for this // samples must be dropped. uint32 current_timestamp = rtp_session->getCurrentTimestamp(); if (seq32_t(timestamp) <= seq32_t(current_timestamp + nsamples)) { if (dtmf_player) { // Send DTMF payload rtp_session->putData(dtmf_rtp_timestamp, payload, sound_payload_size); // If DTMF has ended then set payload back to sound if (dtmf_player->finished()) { set_sound_payload_format(); MEMMAN_DELETE(dtmf_player); delete dtmf_player; dtmf_player = NULL; } } else if (!suppress_samples) { // Send sound samples // Set the expire timeout to the jitter buffer size. // This allows for old packets still to be sent out. rtp_session->setExpireTimeout(MAX_OUT_AUDIO_DELAY_MS * 1000); rtp_session->putData(timestamp, payload, sound_payload_size); } timestamp += nsamples; } else { log_file->write_header("t_audio_rx::run", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Audio rx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": discarded surplus of sound samples.\n"); log_file->write_raw("Timestamp: "); log_file->write_raw(timestamp); log_file->write_endl(); log_file->write_raw("Current timestamp: "); log_file->write_raw(current_timestamp); log_file->write_endl(); log_file->write_raw("nsamples: "); log_file->write_raw(nsamples); log_file->write_endl(); log_file->write_footer(); } // If there is enough data in the DSP buffers to fill another // RTP packet then do not sleep, but immediately go to the // next cycle to play out the data. Probably this thread did // not get enough time, so the buffer filled up. The far end // jitter buffer has to cope with the jitter caused by this. if (is_3way && !is_main_rx_3way) { if (media_3way_peer_rx->size_content() >= SAMPLE_BUF_SIZE) { continue; } } else { if (input_device->get_buffer_space(true) >= SAMPLE_BUF_SIZE) continue; } // There is no data left in the DSP buffers to play out anymore. // So the timestamp must be in sync with the clock of the ccRTP // stack. It might get behind if the sound cards samples a bit // slower than the set sample rate. Advance the timestamp to get // in sync again. current_timestamp = rtp_session->getCurrentTimestamp(); if (seq32_t(timestamp) <= seq32_t(current_timestamp - (JITTER_BUF_MS / audio_encoder->get_ptime()) * nsamples)) { timestamp += nsamples * (JITTER_BUF_MS / audio_encoder->get_ptime()); log_file->write_header("t_audio_rx::run", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Audio rx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": timestamp forwarded by "); log_file->write_raw(nsamples * (JITTER_BUF_MS / audio_encoder->get_ptime())); log_file->write_endl(); log_file->write_raw("Timestamp: "); log_file->write_raw(timestamp); log_file->write_endl(); log_file->write_raw("Current timestamp: "); log_file->write_raw(current_timestamp); log_file->write_endl(); log_file->write_raw("nsamples: "); log_file->write_raw(nsamples); log_file->write_endl(); log_file->write_footer(); } } phone->remove_prohibited_thread(); ui->remove_prohibited_thread(); is_running = false; } void t_audio_rx::set_pt_telephone_event(int pt) { pt_telephone_event = pt; } void t_audio_rx::push_dtmf(char digit, bool inband) { // Ignore invalid DTMF digits if (!is_valid_dtmf_sym(digit)) return; // Ignore DTMF tones in a 3-way conference if (is_3way) return; t_dtmf_event dtmf_event; dtmf_event.dtmf_tone = char2dtmf_ev(digit); dtmf_event.inband = inband; mtx_dtmf_q.lock(); dtmf_queue.push(dtmf_event); mtx_dtmf_q.unlock(); sema_dtmf_q.up(); } t_line *t_audio_rx::get_line(void) const { return audio_session->get_line(); } void t_audio_rx::join_3way(bool main_rx, t_audio_rx *peer_rx) { mtx_3way.lock(); if (is_3way) { log_file->write_header("t_audio_rx::join_3way", LOG_NORMAL); log_file->write_raw("ERROR: audio rx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(" - 3way is already active.\n"); log_file->write_footer(); mtx_3way.unlock(); return; } // Logging log_file->write_header("t_audio_rx::join_3way"); log_file->write_raw("Audio rx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": join 3-way.\n"); if (main_rx) { log_file->write_raw("Role is: mixer.\n"); } else { log_file->write_raw("Role is: non-mixing.\n"); } if (peer_rx) { log_file->write_raw("A peer receiver already exists.\n"); } else { log_file->write_raw("A peer receiver does not exist.\n"); } log_file->write_footer(); // Create media buffers for the 2 far-ends of a 3-way call. // The size of the media buffer is the size of the jitter buffer. // This allows for jitter in the RTP streams and also for // incompatible payload sizes. Eg. 1 far-end may send 20ms paylaods, // while the other sends 30ms payloads. The outgoing RTP stream might // even have another payload size. // When the data has been captured from the soundcard, it will be // checked if there is enough data available in the media buffers, i.e. // the same amount of data as captured from the soundcard for mixing. // If there is it will be retrieved and mixed. // If there isn't the captured sound will simply be sent on its own // to the far-end. Meanwhile the buffer will fill up with data such // that from the next captured sample there will be sufficient data // for mixing. media_3way_peer_tx = new t_media_buffer( JITTER_BUF_SIZE(audio_encoder->get_sample_rate())); MEMMAN_NEW(media_3way_peer_tx); media_3way_peer_rx = new t_media_buffer( JITTER_BUF_SIZE(audio_encoder->get_sample_rate())); MEMMAN_NEW(media_3way_peer_rx); // Create a mix buffer for one sample frame. mix_buf_3way = new unsigned char[SAMPLE_BUF_SIZE]; MEMMAN_NEW_ARRAY(mix_buf_3way); peer_rx_3way = peer_rx; is_3way = true; is_main_rx_3way = main_rx; // Stop DTMF tones as these are not supported in a 3way if (dtmf_player) { MEMMAN_DELETE(dtmf_player); delete dtmf_player; dtmf_player = NULL; } mtx_3way.unlock(); } void t_audio_rx::set_peer_rx_3way(t_audio_rx *peer_rx) { mtx_3way.lock(); if (!is_3way) { mtx_3way.unlock(); return; } // Logging log_file->write_header("t_audio_rx::set_peer_rx_3way"); log_file->write_raw("Audio rx line "); log_file->write_raw(get_line()->get_line_number()+1); if (peer_rx) { log_file->write_raw(": set peer receiver.\n"); } else { log_file->write_raw(": erase peer receiver.\n"); } if (is_main_rx_3way) { log_file->write_raw("Role is: mixer.\n"); } else { log_file->write_raw("Role is: non-mixing.\n"); } log_file->write_footer(); peer_rx_3way = peer_rx; mtx_3way.unlock(); } void t_audio_rx::set_main_rx_3way(bool main_rx) { mtx_3way.lock(); if (!is_3way) { mtx_3way.unlock(); return; } // Logging log_file->write_header("t_audio_rx::set_main_rx_3way"); log_file->write_raw("Audio rx line "); log_file->write_raw(get_line()->get_line_number()+1); if (main_rx) { log_file->write_raw(": change role to: mixer.\n"); } else { log_file->write_raw(": change role to: non-mixing.\n"); } log_file->write_footer(); // Initialize the DSP if we become the mixer and we were not before if (main_rx && !is_main_rx_3way) { // Enable recording if (sys_config->equal_audio_dev(sys_config->get_dev_speaker(), sys_config->get_dev_mic())) { input_device->enable(true, true); } else { input_device->enable(false, true); } // If the stream is stopped for call-hold, then the buffer might // be filled with old sound samples. input_device->flush(false, true); } is_main_rx_3way = main_rx; mtx_3way.unlock(); } void t_audio_rx::stop_3way(void) { mtx_3way.lock(); if (!is_3way) { log_file->write_header("t_audio_rx::stop_3way"); log_file->write_raw("ERROR: audio rx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(" - 3way is not active.\n"); log_file->write_footer(); mtx_3way.unlock(); return; } // Logging log_file->write_header("t_audio_rx::stop_3way"); log_file->write_raw("Audio rx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": stop 3-way.\n"); log_file->write_footer(); is_3way = false; is_main_rx_3way = false; peer_rx_3way = NULL; MEMMAN_DELETE(media_3way_peer_tx); delete media_3way_peer_tx; media_3way_peer_tx = NULL; MEMMAN_DELETE(media_3way_peer_rx); delete media_3way_peer_rx; media_3way_peer_rx = NULL; MEMMAN_DELETE_ARRAY(mix_buf_3way); delete [] mix_buf_3way; mix_buf_3way = NULL; mtx_3way.unlock(); } void t_audio_rx::post_media_peer_tx_3way(unsigned char *media, int len, unsigned short peer_sample_rate) { mtx_3way.lock(); if (!is_3way) { // This is not a 3-way call. This is not necessarily an // error condition. The 3rd party may be in the process of // leaving the conference. // Simply discard the posted media mtx_3way.unlock(); return; } if (peer_sample_rate != audio_encoder->get_sample_rate()) { // Resample media from peer to sample rate of this receiver int output_len = (len / 2) * audio_encoder->get_sample_rate() / peer_sample_rate; short *output_buf = new short[output_len]; MEMMAN_NEW_ARRAY(output_buf); int resample_len = resample((short *)media, len / 2, peer_sample_rate, output_buf, output_len, audio_encoder->get_sample_rate()); media_3way_peer_tx->add((unsigned char *)output_buf, resample_len * 2); MEMMAN_DELETE_ARRAY(output_buf); delete [] output_buf; } else { media_3way_peer_tx->add(media, len); } mtx_3way.unlock(); } void t_audio_rx::post_media_peer_rx_3way(unsigned char *media, int len, unsigned short peer_sample_rate) { mtx_3way.lock(); if (!is_3way) { // This is not a 3-way call. This is not necessarily an // error condition. The 3rd party may be in the process of // leaving the conference. // Simply discard the posted media mtx_3way.unlock(); return; } if (peer_sample_rate != audio_encoder->get_sample_rate()) { // Resample media from peer to sample rate of this receiver int output_len = (len / 2) * audio_encoder->get_sample_rate() / peer_sample_rate; short *output_buf = new short[output_len]; MEMMAN_NEW_ARRAY(output_buf); int resample_len = resample((short *)media, len / 2, peer_sample_rate, output_buf, output_len, audio_encoder->get_sample_rate()); media_3way_peer_rx->add((unsigned char *)output_buf, resample_len * 2); MEMMAN_DELETE_ARRAY(output_buf); delete [] output_buf; } else { media_3way_peer_rx->add(media, len); } mtx_3way.unlock(); } bool t_audio_rx::get_is_main_rx_3way(void) const { return is_main_rx_3way; } twinkle-1.10.1/src/audio/audio_rx.h000066400000000000000000000145261277565361200171500ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _AUDIO_RX_H #define _AUDIO_RX_H // Receive audio from the soundcard and send it to the RTP thread. #include #include #include #include "audio_codecs.h" #include "audio_device.h" #include "audio_encoder.h" #include "dtmf_player.h" #include "media_buffer.h" #include "user.h" #include "threads/mutex.h" #include "threads/sema.h" #include "twinkle_rtp_session.h" #include "twinkle_config.h" #ifdef HAVE_SPEEX #include #include #endif using namespace std; using namespace ost; // Forward declarations class t_audio_session; class t_line; class t_audio_rx { private: // audio_session owning this audio receiver t_audio_session *audio_session; // User profile of user using the line // This is a pointer to the user_config owned by a phone user. // So this pointer should never be deleted. t_user *user_config; // file descriptor audio capture device t_audio_io* input_device; // RTP session t_twinkle_rtp_session *rtp_session; // Media buffer to buffer media from the peer audio trasmitter in a // 3-way call. This media stream will be mixed with the // audio captured from the soundcard. t_media_buffer *media_3way_peer_tx; // Media captured by the peer audio receiver in a 3-way conference t_media_buffer *media_3way_peer_rx; // The peer audio receiver in a 3-way conference. t_audio_rx *peer_rx_3way; // Buffer for mixing purposes in 3-way conference. unsigned char *mix_buf_3way; // Indicates if this receiver is part of a 3-way conference call bool is_3way; // Indicates if this is this receiver has to capture sound from the // soundcard. In a 3-way call, one receiver captures sound, while the // other receiver simply takes the sound from the main receiver. bool is_main_rx_3way; // Mutex to protect actions on 3-way conference data t_mutex mtx_3way; // Audio encoder t_audio_encoder *audio_encoder; // Buffer to store PCM samples for ptime ms unsigned char *input_sample_buf; // Indicates if NAT keep alive packets must be sent during silence // suppression. bool use_nat_keepalive; // RTP payload unsigned short payload_size; unsigned char *payload; unsigned short nsamples; // number of samples taken per packet // Payload type for telephone-event payload. int pt_telephone_event; // Queue of DTMF tones to be sent struct t_dtmf_event { t_dtmf_ev dtmf_tone; bool inband; }; queue dtmf_queue; t_mutex mtx_dtmf_q; t_semaphore sema_dtmf_q; // DTMF player t_dtmf_player *dtmf_player; // Inidicates if the recording thread is running volatile bool is_running; // The thread exits when this indicator is set to true volatile bool stop_running; // Indicates if a capture failure was already logged (log throttling). bool logged_capture_failure; // Timestamp for next RTP packet unsigned long timestamp; #ifdef HAVE_SPEEX /** Speex preprocessor state */ SpeexPreprocessState *speex_preprocess_state; /** Speex VAD enabled? */ bool speex_dsp_vad; #endif // Get sound samples for 1 RTP packet from the soundcard. // Returns false if the main loop has to start another cycle to get // samples (eg. no samples available yet). // If not enough samples are available yet, then a 1 ms sleep will be taken. // Also returns false if capturing samples from the soundcard failed. // Returns true if sounds samples are received. The samples are stored // in the payload buffer in the proper encoding. // The number bytes of the sound payload is returned in sound_payload_size // The silence flag indicates if the returned sound samples represent silence // that may be suppressed. bool get_sound_samples(unsigned short &sound_payload_size, bool &silence); // Get next DTMF event generated by the user. // Returns false if there is no next DTMF event bool get_dtmf_event(void); // Set RTP payload for outgoing sound packets based on the codec. void set_sound_payload_format(void); public: // Create the audio receiver // _fd file descriptor of capture device // _rtp_session RTP socket tp send the RTP stream // _codec audio codec to use // _ptime length of the audio packets in ms // _ptime = 0 means use default ptime value for the codec t_audio_rx(t_audio_session *_audio_session, t_audio_io *_input_device, t_twinkle_rtp_session *_rtp_session, t_audio_codec _codec, unsigned short _payload_id, unsigned short _ptime = 0); ~t_audio_rx(); // Set the is running flag void set_running(bool running); void run(void); // Set the dynamic payload type for telephone events void set_pt_telephone_event(int pt); // Push a new DTMF tone in the DTMF queue void push_dtmf(char digit, bool inband); // Get phone line belonging to this audio transmitter t_line *get_line(void) const; // Join a 3-way conference call. // main_rx indicates if this receiver must be the main receiver capturing // the sound from the soundcard. // The peer_rx is the peer receiver (may be NULL( void join_3way(bool main_rx, t_audio_rx *peer_rx); // Change the peer receiver in a 3-way (set to NULL to erase). void set_peer_rx_3way(t_audio_rx *peer_rx); // Change the main rx role in a 3-way void set_main_rx_3way(bool main_rx); // Delete 3rd party from a 3-way conference. void stop_3way(void); // Post media from the peer transmitter in a 3-way. void post_media_peer_tx_3way(unsigned char *media, int len, unsigned short peer_sample_rate); // Post media from the peer receiver in a 3-way. void post_media_peer_rx_3way(unsigned char *media, int len, unsigned short peer_sample_rate); // Returns if this receiver is the main receiver in a 3-way bool get_is_main_rx_3way(void) const; }; #endif twinkle-1.10.1/src/audio/audio_session.cpp000066400000000000000000000424231277565361200205320ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "twinkle_config.h" #include #include #include #include #include #include "audio_session.h" #include "line.h" #include "log.h" #include "sys_settings.h" #include "translator.h" #include "user.h" #include "userintf.h" #include "util.h" #include "audits/memman.h" #ifdef HAVE_ZRTP #include "twinkle_zrtp_ui.h" #endif static t_audio_session *_audio_session; /////////// // PRIVATE /////////// bool t_audio_session::is_3way(void) const { t_line *l = get_line(); t_phone *p = l->get_phone(); return p->part_of_3way(l->get_line_number()); } t_audio_session *t_audio_session::get_peer_3way(void) const { t_line *l = get_line(); t_phone *p = l->get_phone(); t_line *peer_line = p->get_3way_peer_line(l->get_line_number()); return peer_line->get_audio_session(); } bool t_audio_session::open_dsp(void) { if (sys_config->equal_audio_dev(sys_config->get_dev_speaker(), sys_config->get_dev_mic())) { return open_dsp_full_duplex(); } return open_dsp_speaker() && open_dsp_mic(); } bool t_audio_session::open_dsp_full_duplex(void) { // Open audio device speaker = t_audio_io::open(sys_config->get_dev_speaker(), true, true, true, 1, SAMPLEFORMAT_S16, audio_sample_rate(codec), true); if (!speaker) { string msg(TRANSLATE2("CoreAudio", "Failed to open sound card")); log_file->write_report(msg, "t_audio_session::open_dsp_full_duplex", LOG_NORMAL, LOG_CRITICAL); ui->cb_display_msg(msg, MSG_CRITICAL); return false; } // Disable recording // If recording is not disabled, then the capture buffers will // already fill with data. Then when the audio_rx thread starts // to read blocks of 160 samples, it gets all these initial blocks // very quickly 1 per 12 ms I have seen. And hence the timestamps // for these blocks get out of sync with the RTP stack. // Also a large delay is introduced by this. So recording should // be enabled just before the data is read from the device. speaker->enable(true, false); mic = speaker; return true; } bool t_audio_session::open_dsp_speaker(void) { speaker = t_audio_io::open(sys_config->get_dev_speaker(), true, false, true, 1, SAMPLEFORMAT_S16, audio_sample_rate(codec), true); if (!speaker) { string msg(TRANSLATE2("CoreAudio", "Failed to open sound card")); log_file->write_report(msg, "t_audio_session::open_dsp_speaker", LOG_NORMAL, LOG_CRITICAL); ui->cb_display_msg(msg, MSG_CRITICAL); return false; } return true; } bool t_audio_session::open_dsp_mic(void) { mic = t_audio_io::open(sys_config->get_dev_mic(), false, true, true, 1, SAMPLEFORMAT_S16, audio_sample_rate(codec), true); if (!mic) { string msg(TRANSLATE2("CoreAudio", "Failed to open sound card")); log_file->write_report(msg, "t_audio_session::open_dsp_mic", LOG_NORMAL, LOG_CRITICAL); ui->cb_display_msg(msg, MSG_CRITICAL); return false; } // Disable recording // If recording is not disabled, then the capture buffers will // already fill with data. Then when the audio_rx thread starts // to read blocks of 160 samples, it gets all these initial blocks // very quickly 1 per 12 ms I have seen. And hence the timestamps // for these blocks get out of sync with the RTP stack. // Also a large delay is introduced by this. So recording should // be enabled just before the data is read from the device. speaker->enable(true, false); return true; } /////////// // PUBLIC /////////// t_audio_session::t_audio_session(t_session *_session, const string &_recv_host, unsigned short _recv_port, const string &_dst_host, unsigned short _dst_port, t_audio_codec _codec, unsigned short _ptime, const map &recv_payload2ac, const map &send_ac2payload, bool encrypt) { valid = false; session = _session; audio_rx = NULL; audio_tx = NULL; thr_audio_rx = NULL; thr_audio_tx = NULL; speaker = NULL; mic = NULL; codec = _codec; ptime = _ptime; is_encrypted = false; zrtp_sas.clear(); // Assume the SAS is confirmed. When a SAS is received from the ZRTP // stack, the confirmed flag will be cleared. zrtp_sas_confirmed = true; srtp_cipher_mode.clear(); log_file->write_header("t_audio_session::t_audio_session"); log_file->write_raw("Receive RTP from: "); log_file->write_raw(_recv_host); log_file->write_raw(":"); log_file->write_raw(_recv_port); log_file->write_endl(); log_file->write_raw("Send RTP to: "); log_file->write_raw(_dst_host); log_file->write_raw(":"); log_file->write_raw(_dst_port); log_file->write_endl(); log_file->write_footer(); t_user *user_config = get_line()->get_user(); // Create RTP session try { if (_recv_host.empty() || _recv_port == 0) { rtp_session = new t_twinkle_rtp_session( InetHostAddress("0.0.0.0")); MEMMAN_NEW(rtp_session); } else { rtp_session = new t_twinkle_rtp_session( InetHostAddress(_recv_host.c_str()), _recv_port); MEMMAN_NEW(rtp_session); } #ifdef HAVE_ZRTP ZrtpQueue* zque = dynamic_cast(rtp_session); if (zque && rtp_session->is_zrtp_initialized()) { zque->setEnableZrtp(encrypt); if (user_config->get_zrtp_enabled()) { // Create the ZRTP call back interface TwinkleZrtpUI* twui = new TwinkleZrtpUI(this); // The ZrtpQueue keeps track of the twui - the destructor of // ZrtpQueue (aka t_twinkle_rtp_session) deletes this object, // thus no other management is required. zque->setUserCallback(twui); } } #endif } catch(...) { // If the RTPSession constructor throws an exception, no // object is created, so clear the pointer. rtp_session = NULL; string msg(TRANSLATE2("CoreAudio", "Failed to create a UDP socket (RTP) on port %1")); msg = replace_first(msg, "%1", int2str(_recv_port)); log_file->write_report(msg, "t_audio_session::t_audio_session", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); return; } if (!_dst_host.empty() && _dst_port != 0) { rtp_session->addDestination( InetHostAddress(_dst_host.c_str()), _dst_port); } // Set payload format for outgoing RTP packets map::const_iterator it; it = send_ac2payload.find(codec); assert(it != send_ac2payload.end()); unsigned short payload_id = it->second; rtp_session->setPayloadFormat(DynamicPayloadFormat( payload_id, audio_sample_rate(codec))); // Open and initialize sound card t_audio_session *as_peer; if (is_3way() && (as_peer = get_peer_3way())) { speaker = as_peer->get_dsp_speaker(); mic = as_peer->get_dsp_mic(); if (!speaker || !mic) return; } else { if (!open_dsp()) return; } #ifdef HAVE_SPEEX // Speex AEC auxiliary data initialization do_echo_cancellation = false; if (user_config->get_speex_dsp_aec()) { int nsamples = audio_sample_rate(codec) / 1000 * ptime; speex_echo_state = speex_echo_state_init(nsamples, 5*nsamples); do_echo_cancellation = true; echo_captured_last = true; } #endif // Create recorder if (!_recv_host.empty() && _recv_port != 0) { audio_rx = new t_audio_rx(this, mic, rtp_session, codec, payload_id, ptime); MEMMAN_NEW(audio_rx); // Setup 3-way configuration if this audio session is part of // a 3-way. if (is_3way()) { t_audio_session *peer = get_peer_3way(); if (!peer || !peer->audio_rx) { // There is no peer rx yet, so become the main rx audio_rx->join_3way(true, NULL); if (peer && peer->audio_tx) { peer->audio_tx->set_peer_rx_3way(audio_rx); } } else { // There is a peer rx already so that must be the // main rx. audio_rx->join_3way(false, peer->audio_rx); peer->audio_rx->set_peer_rx_3way(audio_rx); if (peer->audio_tx) { peer->audio_tx->set_peer_rx_3way(audio_rx); } } } } // Create player if (!_dst_host.empty() && _dst_port != 0) { audio_tx = new t_audio_tx(this, speaker, rtp_session, codec, recv_payload2ac, ptime); MEMMAN_NEW(audio_tx); // Setup 3-way configuration if this audio session is part of // a 3-way. if (is_3way()) { t_audio_session *peer = get_peer_3way(); if (!peer) { // There is no peer tx yet, so become the mixer tx audio_tx->join_3way(true, NULL, NULL); } else if (!peer->audio_tx) { // There is a peer audio session, but no peer tx, // so become the mixer tx audio_tx->join_3way(true, NULL, peer->audio_rx); } else { // There is a peer tx already. That must be the // mixer. audio_tx->join_3way( false, peer->audio_tx, peer->audio_rx); } } } valid = true; } t_audio_session::~t_audio_session() { // Delete of the audio_rx and audio_tx objects will terminate // thread execution. if (audio_rx) { // Reconfigure 3-way configuration if this audio session is // part of a 3-way. if (is_3way()) { t_audio_session *peer = get_peer_3way(); if (peer) { // Make the peer audio rx the main rx and remove // reference to this audio rx if (peer->audio_rx) { peer->audio_rx->set_peer_rx_3way(NULL); peer->audio_rx->set_main_rx_3way(true); } // Remove reference to this audio rx if (peer->audio_tx) { peer->audio_tx->set_peer_rx_3way(NULL); } } } MEMMAN_DELETE(audio_rx); delete audio_rx; } if (audio_tx) { // Reconfigure 3-way configuration if this audio session is // part of a 3-way. if (is_3way()) { t_audio_session *peer = get_peer_3way(); if (peer) { // Make the peer audio tx the mixer and remove // reference to this audio tx if (peer->audio_tx) { peer->audio_tx->set_peer_tx_3way(NULL); peer->audio_tx->set_mixer_3way(true); } } } MEMMAN_DELETE(audio_tx); delete audio_tx; } if (thr_audio_rx) { MEMMAN_DELETE(thr_audio_rx); delete thr_audio_rx; } if (thr_audio_tx) { MEMMAN_DELETE(thr_audio_tx); delete thr_audio_tx; } if (rtp_session) { log_file->write_header("t_audio_session::~t_audio_session"); log_file->write_raw("Line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": stopping RTP session.\n"); log_file->write_footer(); MEMMAN_DELETE(rtp_session); delete rtp_session; log_file->write_header("t_audio_session::~t_audio_session"); log_file->write_raw("Line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": RTP session stopped.\n"); log_file->write_footer(); } if (speaker && (!is_3way() || !get_peer_3way())) { if (mic == speaker) mic = 0; MEMMAN_DELETE(speaker); delete speaker; speaker = 0; } if (mic && (!is_3way() || !get_peer_3way())) { MEMMAN_DELETE(mic); delete mic; mic = 0; } #ifdef HAVE_SPEEX // cleaning speech AEC if (do_echo_cancellation) { speex_echo_state_destroy(speex_echo_state); } #endif } void t_audio_session::set_session(t_session *_session) { mtx_session.lock(); session = _session; mtx_session.unlock(); } void t_audio_session::run(void) { _audio_session = this; log_file->write_header("t_audio_session::run"); log_file->write_raw("Line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": starting RTP session.\n"); log_file->write_footer(); rtp_session->startRunning(); log_file->write_header("t_audio_session::run"); log_file->write_raw("Line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": RTP session started.\n"); log_file->write_footer(); if (audio_rx) { try { // Set the running flag now instead of at the start of // t_audio_tx::run as due to race conditions the thread might // get destroyed before the run method starts running. The // destructor still has to wait on the thread to finish. audio_rx->set_running(true); thr_audio_rx = new t_thread(main_audio_rx, NULL); MEMMAN_NEW(thr_audio_rx); // thr_audio_rx->set_sched_fifo(90); thr_audio_rx->detach(); } catch (int) { audio_rx->set_running(false); string msg(TRANSLATE2("CoreAudio", "Failed to create audio receiver thread.")); log_file->write_report(msg, "t_audio_session::run", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); exit(1); } } if (audio_tx) { try { // See comment above for audio_rx audio_tx->set_running(true); thr_audio_tx = new t_thread(main_audio_tx, NULL); MEMMAN_NEW(thr_audio_tx); // thr_audio_tx->set_sched_fifo(90); thr_audio_tx->detach(); } catch (int) { audio_tx->set_running(false); string msg(TRANSLATE2("CoreAudio", "Failed to create audio transmitter thread.")); log_file->write_report(msg, "t_audio_session::run", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); exit(1); } } } void t_audio_session::set_pt_out_dtmf(unsigned short pt) { if (audio_rx) audio_rx->set_pt_telephone_event(pt); } void t_audio_session::set_pt_in_dtmf(unsigned short pt, unsigned short pt_alt) { if (audio_tx) audio_tx->set_pt_telephone_event(pt, pt_alt); } void t_audio_session::send_dtmf(char digit, bool inband) { if (audio_rx) audio_rx->push_dtmf(digit, inband); } t_line *t_audio_session::get_line(void) const { t_line *line; mtx_session.lock(); line = session->get_line(); mtx_session.unlock(); return line; } void t_audio_session::start_3way(void) { if (audio_rx) { audio_rx->join_3way(true, NULL); } if (audio_tx) { audio_tx->join_3way(true, NULL, NULL); } } void t_audio_session::stop_3way(void) { if (audio_rx) { t_audio_session *peer = get_peer_3way(); if (peer) { if (peer->audio_rx) { peer->audio_rx->set_peer_rx_3way(NULL); } if (peer->audio_tx) { peer->audio_tx->set_peer_rx_3way(NULL); } } audio_rx->stop_3way(); } if (audio_tx) { t_audio_session *peer = get_peer_3way(); if (peer) { if (peer->audio_tx) { peer->audio_tx->set_peer_tx_3way(NULL); } } audio_tx->stop_3way(); } } bool t_audio_session::is_valid(void) const { return valid; } t_audio_io* t_audio_session::get_dsp_speaker(void) const { return speaker; } t_audio_io* t_audio_session::get_dsp_mic(void) const { return mic; } bool t_audio_session::matching_sample_rates(void) const { int codec_sample_rate = audio_sample_rate(codec); return (speaker->get_sample_rate() == codec_sample_rate && mic->get_sample_rate() == codec_sample_rate); } void t_audio_session::confirm_zrtp_sas(void) { #ifdef HAVE_ZRTP ZrtpQueue* zque = dynamic_cast(rtp_session); if (zque) { zque->SASVerified(); set_zrtp_sas_confirmed(true); } #endif } void t_audio_session::reset_zrtp_sas_confirmation(void) { #ifdef HAVE_ZRTP ZrtpQueue* zque = dynamic_cast(rtp_session); if (zque) { zque->resetSASVerified(); set_zrtp_sas_confirmed(false); } #endif } void t_audio_session::enable_zrtp(void) { #ifdef HAVE_ZRTP ZrtpQueue* zque = dynamic_cast(rtp_session); if (zque) { zque->setEnableZrtp(true); } #endif } void t_audio_session::zrtp_request_go_clear(void) { #ifdef HAVE_ZRTP ZrtpQueue* zque = dynamic_cast(rtp_session); if (zque) { zque->requestGoClear(); } #endif } void t_audio_session::zrtp_go_clear_ok(void) { #ifdef HAVE_ZRTP ZrtpQueue* zque = dynamic_cast(rtp_session); if (zque) { zque->goClearOk(); } #endif } bool t_audio_session::get_is_encrypted(void) const { mtx_zrtp_data.lock(); bool b = is_encrypted; mtx_zrtp_data.unlock(); return b; } string t_audio_session::get_zrtp_sas(void) const { mtx_zrtp_data.lock(); string s = zrtp_sas; mtx_zrtp_data.unlock(); return s; } bool t_audio_session::get_zrtp_sas_confirmed(void) const { mtx_zrtp_data.lock(); bool b = zrtp_sas_confirmed; mtx_zrtp_data.unlock(); return b; } string t_audio_session::get_srtp_cipher_mode(void) const { mtx_zrtp_data.lock(); string s = srtp_cipher_mode; mtx_zrtp_data.unlock(); return s; } void t_audio_session::set_is_encrypted(bool on) { mtx_zrtp_data.lock(); is_encrypted = on; mtx_zrtp_data.unlock(); } void t_audio_session::set_zrtp_sas(const string &sas) { mtx_zrtp_data.lock(); zrtp_sas = sas; mtx_zrtp_data.unlock(); } void t_audio_session::set_zrtp_sas_confirmed(bool confirmed) { mtx_zrtp_data.lock(); zrtp_sas_confirmed = confirmed; mtx_zrtp_data.unlock(); } void t_audio_session::set_srtp_cipher_mode(const string &cipher_mode) { mtx_zrtp_data.lock(); srtp_cipher_mode = cipher_mode; mtx_zrtp_data.unlock(); } #ifdef HAVE_SPEEX bool t_audio_session::get_do_echo_cancellation(void) const { return do_echo_cancellation; } bool t_audio_session::get_echo_captured_last(void) { return echo_captured_last; } void t_audio_session::set_echo_captured_last(bool value) { echo_captured_last = value; } SpeexEchoState *t_audio_session::get_speex_echo_state(void) { return speex_echo_state; } #endif void *main_audio_rx(void *arg) { _audio_session->audio_rx->run(); return NULL; } void *main_audio_tx(void *arg) { _audio_session->audio_tx->run(); return NULL; } twinkle-1.10.1/src/audio/audio_session.h000066400000000000000000000114541277565361200201770ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _AUDIO_SESSION_H #define _AUDIO_SESSION_H #include #include #include "audio_rx.h" #include "audio_tx.h" #include "session.h" #include "twinkle_rtp_session.h" #include "threads/thread.h" #include "threads/mutex.h" #ifdef HAVE_SPEEX #include #endif using namespace std; using namespace ost; // Forward declarations class t_session; class t_line; class t_audio_session { private: // SIP session owning this audio session t_session *session; /** Mutex for concurrent access to the session. */ mutable t_mutex mtx_session; // This flag indicates if the created audio session is valid. // It might be invalid because, the RTP session could not be created // or the soundcard could not be opened. bool valid; // file descriptor audio device t_audio_io *speaker; t_audio_io *mic; t_twinkle_rtp_session *rtp_session; t_audio_codec codec; unsigned short ptime; // in milliseconds t_thread *thr_audio_rx; // recording thread t_thread *thr_audio_tx; // playing thread // ZRTP info mutable t_mutex mtx_zrtp_data; bool is_encrypted; string zrtp_sas; bool zrtp_sas_confirmed; string srtp_cipher_mode; #ifdef HAVE_SPEEX // Indicator whether to use (Speex) AEC bool do_echo_cancellation; // Indicator whether the last operation of (Speex) AEC, // speex_echo_capture or speex_echo_playback, was the speex_echo_capture bool echo_captured_last; // speex AEC state SpeexEchoState *speex_echo_state; #endif // 3-way conference data // Returns if this audio session is part of a 3-way conference bool is_3way(void) const; // Returns the peer audio session of a 3-way conference t_audio_session *get_peer_3way(void) const; // Open the sound card bool open_dsp(void); bool open_dsp_full_duplex(void); bool open_dsp_speaker(void); bool open_dsp_mic(void); public: t_audio_rx *audio_rx; t_audio_tx *audio_tx; t_audio_session(t_session *_session, const string &_recv_host, unsigned short _recv_port, const string &_dst_host, unsigned short _dst_port, t_audio_codec _codec, unsigned short _ptime, const map &recv_payload2ac, const map &send_ac2payload, bool encrypt); ~t_audio_session(); void run(void); /** * Change the owning session. * @param _session New session owning this audio session. */ void set_session(t_session *_session); // Set outgoing/incoming DTMF dynamic payload types void set_pt_out_dtmf(unsigned short pt); void set_pt_in_dtmf(unsigned short pt, unsigned short pt_alt); // Send DTMF digit void send_dtmf(char digit, bool inband); // Get the line that belongs to this audio session t_line *get_line(void) const; // Become the first session in a 3-way conference void start_3way(void); // Leave a 3-way conference void stop_3way(void); // Check if audio session is valid bool is_valid(void) const; // Get pointer for soundcard I/O object t_audio_io* get_dsp_speaker(void) const; t_audio_io* get_dsp_mic(void) const; // Check if sample rate from speaker and mic match with sample rate // from codec. The sample rates might not match due to 3-way conference // calls with mixed sample rate bool matching_sample_rates(void) const; // ZRTP actions void confirm_zrtp_sas(void); void reset_zrtp_sas_confirmation(void); void enable_zrtp(void); void zrtp_request_go_clear(void); void zrtp_go_clear_ok(void); // ZRTP data manipulations bool get_is_encrypted(void) const; string get_zrtp_sas(void) const; bool get_zrtp_sas_confirmed(void) const; string get_srtp_cipher_mode(void) const; void set_is_encrypted(bool on); void set_zrtp_sas(const string &sas); void set_zrtp_sas_confirmed(bool confirmed); void set_srtp_cipher_mode(const string &cipher_mode); #ifdef HAVE_SPEEX // speex acoustic echo cancellation (AEC) manipulations bool get_do_echo_cancellation(void) const; bool get_echo_captured_last(void); void set_echo_captured_last(bool value); SpeexEchoState *get_speex_echo_state(void); #endif }; // Main functions for rx and tx threads void *main_audio_rx(void *arg); void *main_audio_tx(void *arg); #endif twinkle-1.10.1/src/audio/audio_tx.cpp000066400000000000000000000732631277565361200175100ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include #include #include "audio_tx.h" #include "log.h" #include "phone.h" #include "userintf.h" #include "util.h" #include "line.h" #include "sequence_number.h" #include "audits/memman.h" extern t_phone *phone; #define SAMPLE_BUF_SIZE (MAX_PTIME * sc_sample_rate/1000 * AUDIO_SAMPLE_SIZE/8) // Debug macro to print timestamp #define DEBUG_TS(s) { gettimeofday(&debug_timer, NULL);\ cout << "DEBUG: ";\ cout << debug_timer.tv_sec * 1000 +\ debug_timer.tv_usec / 1000;\ cout << ":" << debug_timer.tv_sec * 1000 + debug_timer.tv_usec / 1000 - (debug_timer_prev.tv_sec * 1000 + debug_timer_prev.tv_usec / 1000);\ cout << " " << (s) << endl;\ debug_timer_prev = debug_timer;\ } ////////// // PUBLIC ////////// t_audio_tx::t_audio_tx(t_audio_session *_audio_session, t_audio_io *_playback_device, t_twinkle_rtp_session *_rtp_session, t_audio_codec _codec, const map &_payload2codec, unsigned short _ptime) { audio_session = _audio_session; user_config = audio_session->get_line()->get_user(); assert(user_config); playback_device = _playback_device; rtp_session = _rtp_session; codec = _codec; sc_sample_rate = audio_sample_rate(_codec); payload2codec = _payload2codec; is_running = false; stop_running = false; // Create audio decoders map_audio_decoder[CODEC_G711_ALAW] = new t_g711a_audio_decoder(_ptime, user_config); MEMMAN_NEW(map_audio_decoder[CODEC_G711_ALAW]); map_audio_decoder[CODEC_G711_ULAW] = new t_g711u_audio_decoder(_ptime, user_config); MEMMAN_NEW(map_audio_decoder[CODEC_G711_ULAW]); map_audio_decoder[CODEC_GSM] = new t_gsm_audio_decoder(user_config); MEMMAN_NEW(map_audio_decoder[CODEC_GSM]); #ifdef HAVE_SPEEX map_audio_decoder[CODEC_SPEEX_NB] = new t_speex_audio_decoder( t_speex_audio_decoder::MODE_NB, user_config); MEMMAN_NEW(map_audio_decoder[CODEC_SPEEX_NB]); map_audio_decoder[CODEC_SPEEX_WB] = new t_speex_audio_decoder( t_speex_audio_decoder::MODE_WB, user_config); MEMMAN_NEW(map_audio_decoder[CODEC_SPEEX_WB]); map_audio_decoder[CODEC_SPEEX_UWB] = new t_speex_audio_decoder( t_speex_audio_decoder::MODE_UWB, user_config); MEMMAN_NEW(map_audio_decoder[CODEC_SPEEX_UWB]); #endif #ifdef HAVE_ILBC map_audio_decoder[CODEC_ILBC] = new t_ilbc_audio_decoder(_ptime, user_config); MEMMAN_NEW(map_audio_decoder[CODEC_ILBC]); #endif map_audio_decoder[CODEC_G726_16] = new t_g726_audio_decoder( t_g726_audio_decoder::BIT_RATE_16, _ptime, user_config); MEMMAN_NEW(map_audio_decoder[CODEC_G726_16]); map_audio_decoder[CODEC_G726_24] = new t_g726_audio_decoder( t_g726_audio_decoder::BIT_RATE_24, _ptime, user_config); MEMMAN_NEW(map_audio_decoder[CODEC_G726_24]); map_audio_decoder[CODEC_G726_32] = new t_g726_audio_decoder( t_g726_audio_decoder::BIT_RATE_32, _ptime, user_config); MEMMAN_NEW(map_audio_decoder[CODEC_G726_32]); map_audio_decoder[CODEC_G726_40] = new t_g726_audio_decoder( t_g726_audio_decoder::BIT_RATE_40, _ptime, user_config); MEMMAN_NEW(map_audio_decoder[CODEC_G726_40]); #ifdef HAVE_BCG729 map_audio_decoder[CODEC_G729A] = new t_g729a_audio_decoder( _ptime, user_config); MEMMAN_NEW(map_audio_decoder[CODEC_G729A]); #endif ptime = map_audio_decoder[codec]->get_default_ptime(); sample_buf = new unsigned char[SAMPLE_BUF_SIZE]; MEMMAN_NEW_ARRAY(sample_buf); // Create concealment buffers for (int i = 0; i < MAX_CONCEALMENT; i++) { conceal_buf[i] = new unsigned char[SAMPLE_BUF_SIZE]; MEMMAN_NEW_ARRAY(conceal_buf[i]); conceal_buflen[i] = 0; } conceal_num = 0; conceal_pos = 0; // Initialize jitter buffer jitter_buf = new unsigned char[JITTER_BUF_SIZE(sc_sample_rate)]; MEMMAN_NEW_ARRAY(jitter_buf); jitter_buf_len = 0; load_jitter_buf = true; soundcard_buf_size = playback_device->get_buffer_size(false); // Initialize 3-way settings is_3way = false; is_3way_mixer = false; media_3way_peer_tx = NULL; peer_tx_3way = NULL; peer_rx_3way = NULL; mix_buf_3way = NULL; // Initialize telephone event settings pt_telephone_event = -1; pt_telephone_event_alt = 1; } t_audio_tx::~t_audio_tx() { struct timespec sleeptimer; if (is_running) { stop_running = true; do { sleeptimer.tv_sec = 0; sleeptimer.tv_nsec = 10000000; nanosleep(&sleeptimer, NULL); continue; } while (is_running); } MEMMAN_DELETE_ARRAY(sample_buf); delete [] sample_buf; MEMMAN_DELETE_ARRAY(jitter_buf); delete [] jitter_buf; for (int i = 0; i < MAX_CONCEALMENT; i++) { MEMMAN_DELETE_ARRAY(conceal_buf[i]); delete [] conceal_buf[i]; } // Destroy audio decoders for (map::iterator i = map_audio_decoder.begin(); i != map_audio_decoder.end(); i++) { MEMMAN_DELETE(i->second); delete i->second; } // Cleanup 3-way resources if (media_3way_peer_tx) { MEMMAN_DELETE(media_3way_peer_tx); delete media_3way_peer_tx; } if (mix_buf_3way) { MEMMAN_DELETE_ARRAY(mix_buf_3way); delete [] mix_buf_3way; } } void t_audio_tx::retain_for_concealment(unsigned char *buf, unsigned short len) { if (conceal_num == 0) { memcpy(conceal_buf[0], buf, len); conceal_buflen[0] = len; conceal_num = 1; conceal_pos = 0; return; } if (conceal_num < MAX_CONCEALMENT) { memcpy(conceal_buf[conceal_num], buf, len); conceal_buflen[conceal_num] = len; conceal_num++; return; } memcpy(conceal_buf[conceal_pos], buf, len); conceal_buflen[conceal_pos] = len; conceal_pos = (conceal_pos + 1) % MAX_CONCEALMENT; } void t_audio_tx::conceal(short num) { // Some codecs have a PLC. // Only use this PLC is the sound card sample rate equals the codec // sample rate. If they differ, then we should resample the codec // samples. As this should be a rare case, we are lazy here. In // this rare case, use Twinkle's low-tech PLC. if (map_audio_decoder[codec]->has_plc() && audio_sample_rate(codec) == sc_sample_rate) { short *sb = (short *)sample_buf; for (int i = 0; i < num; i++) { int nsamples; nsamples = map_audio_decoder[codec]->conceal(sb, SAMPLE_BUF_SIZE); if (nsamples > 0) { play_pcm(sample_buf, nsamples * 2); } } return; } // Replay previous packets for other codecs short i = (conceal_pos + (MAX_CONCEALMENT - num)) % MAX_CONCEALMENT; if (i >= conceal_pos) { for (int j = i; j < MAX_CONCEALMENT; j++) { play_pcm(conceal_buf[j], conceal_buflen[j]); } for (int j = 0; j < conceal_pos; j++) { play_pcm(conceal_buf[j], conceal_buflen[j]); } } else { for (int j = i; j < conceal_pos; j++) { play_pcm(conceal_buf[j], conceal_buflen[j]); } } } void t_audio_tx::clear_conceal_buf(void) { conceal_pos = 0; conceal_num = 0; } void t_audio_tx::play_pcm(unsigned char *buf, unsigned short len, bool only_3rd_party) { int status; //struct timeval debug_timer, debug_timer_prev; unsigned char *playbuf = buf; // If there is only sound from the 3rd party in a 3-way, then check // if there is still enough sound in the buffer of the DSP to be // played. If not, then play out the sound from the 3rd party only. if (only_3rd_party) { /* Does not work on all ALSA implementations. if (playback_device->get_buffer_space(false) < soundcard_buf_size - len) { */ if (!playback_device->play_buffer_underrun()) { // There is still sound in the DSP buffers to be // played, so let's wait. Maybe in the next cycle // an RTP packet from the far-end will be received. return; } } // If we are in a 3-way then send the samples to the peer audio // receiver for mixing if (!only_3rd_party && is_3way && peer_rx_3way) { peer_rx_3way->post_media_peer_tx_3way(buf, len, sc_sample_rate); } // If we are in a 3-way conference and we are not the mixer then // send the sound samples to the mixer if (is_3way && !is_3way_mixer) { if (peer_tx_3way) { peer_tx_3way->post_media_peer_tx_3way(buf, len, sc_sample_rate); return; } else { // There is no peer. return; } } // Mix audio for 3-way conference if (is_3way && is_3way_mixer) { if (media_3way_peer_tx->get(mix_buf_3way, len)) { short *mix_sb = (short *)mix_buf_3way; short *sb = (short *)buf; for (int i = 0; i < len / 2; i++) { mix_sb[i] = mix_linear_pcm(sb[i], mix_sb[i]); } playbuf = mix_buf_3way; } } // Fill jitter buffer before playing if (load_jitter_buf) { if (jitter_buf_len + len < JITTER_BUF_SIZE(sc_sample_rate)) { memcpy(jitter_buf + jitter_buf_len, playbuf, len); jitter_buf_len += len; } else { // Write the contents of the jitter buffer to the DSP. // The buffers in the DSP will now function as jitter // buffer. status = playback_device->write(jitter_buf, jitter_buf_len); if (status != jitter_buf_len) { string msg("Writing to dsp failed: "); msg += get_error_str(errno); log_file->write_report(msg, "t_audio_tx::play_pcm", LOG_NORMAL, LOG_CRITICAL); } // Write passed sound samples to DSP. status = playback_device->write(playbuf, len); if (status != len) { string msg("Writing to dsp failed: "); msg += get_error_str(errno); log_file->write_report(msg, "t_audio_tx::play_pcm", LOG_NORMAL, LOG_CRITICAL); } load_jitter_buf = false; } return; } // If buffer on soundcard is empty, then the jitter buffer needs // to be refilled. This should only occur when no RTP packets // have been received for a while (silence suppression or packet loss) /* * This code does not work on all ALSA implementations, e.g. ALSA via pulse audio int bufferspace = playback_device->get_buffer_space(false); if (bufferspace == soundcard_buf_size && len <= JITTER_BUF_SIZE(sc_sample_rate)) { */ if (playback_device->play_buffer_underrun()) { memcpy(jitter_buf, playbuf, len); jitter_buf_len = len; load_jitter_buf = true; log_file->write_header("t_audio_tx::play_pcm", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Audio tx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": jitter buffer empty.\n"); log_file->write_footer(); return; } // If the play-out buffer contains the maximum number of // packets then start skipping packets to prevent // unacceptable delay. // This can only happen if the thread did not get // processing time for a while and RTP packets start to // pile up. // Or if a soundcard plays out the samples at just less then // the requested sample rate. /* Not needed anymore, the ::run loop already discards incoming RTP packets with a late timestamp. This seems to solve the slow soundcard problem better. The solution below caused annoying ticks in the playout. if (soundcard_buf_size - bufferspace > JITTER_BUF_SIZE + len) { log_file->write_header("t_audio_tx::play_pcm", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Audio tx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": jitter buffer overflow: "); log_file->write_raw(bufferspace); log_file->write_raw(" bytes.\n"); log_file->write_footer(); return; } */ // Write passed sound samples to DSP. status = playback_device->write(playbuf, len); if (status != len) { string msg("Writing to dsp failed: "); msg += get_error_str(errno); log_file->write_report(msg, "t_audio_tx::play_pcm", LOG_NORMAL, LOG_CRITICAL); return; } } void t_audio_tx::set_running(bool running) { is_running = running; } void t_audio_tx::run(void) { const AppDataUnit* adu; struct timespec sleeptimer; //struct timeval debug_timer, debug_timer_prev; int last_seqnum = -1; // seqnum of last received RTP packet // RTP packets with multiple SSRCs may be received. Each SSRC // represents an audio stream. Twinkle will only play 1 audio stream. // On a reception of a new SSRC, Twinkle will switch over to play the // new stream. This supports devices that change SSRC during a call. uint32 ssrc_current = 0; bool recvd_dtmf = false; // indicates if last RTP packets is a DTMF event // The running flag is set already in t_audio_session::run to prevent // a crash when the thread gets destroyed before it starts running. // is_running = true; uint32 rtp_timestamp = 0; // This thread may not take the lock on the transaction layer to // prevent dead locks phone->add_prohibited_thread(); ui->add_prohibited_thread(); while (true) { do { adu = NULL; if (stop_running) break; rtp_timestamp = rtp_session->getFirstTimestamp(); adu = rtp_session->getData( rtp_session->getFirstTimestamp()); if (adu == NULL || adu->getSize() <= 0) { // There is no packet available. This may have // several reasons: // - the thread scheduling granularity does // not match ptime // - packet lost // - packet delayed // Wait another cycle for a packet. The // jitter buffer will cope with this variation. if (adu) { delete adu; adu = NULL; } // If we are the mixer in a 3-way call and there // is enough media from the other far-end then // this must be sent to the dsp. if (is_3way && is_3way_mixer && media_3way_peer_tx->size_content() >= ptime * (audio_sample_rate(codec) / 1000) * 2) { // Fill the sample buffer with silence int len = ptime * (audio_sample_rate(codec) / 1000) * 2; memset(sample_buf, 0, len); play_pcm(sample_buf, len, true); } // Sleep ptime ms sleeptimer.tv_sec = 0; if (ptime >= 20) { sleeptimer.tv_nsec = ptime * 1000000 - 10000000; } else { // With a thread schedule of 10ms // granularity, this will schedule the // thread every 10ms. sleeptimer.tv_nsec = 5000000; } nanosleep(&sleeptimer, NULL); } } while (adu == NULL || (adu->getSize() <= 0)); if (stop_running) { if (adu) delete adu; break; } if (adu) { // adu is created by ccRTP, but we have to delete it, // so report it to MEMMAN MEMMAN_NEW(const_cast(adu)); } // Check for a codec change map::const_iterator it_codec; it_codec = payload2codec.find(adu->getType()); t_audio_codec recvd_codec = CODEC_NULL; if (it_codec != payload2codec.end()) { recvd_codec = it_codec->second; } // Switch over to new SSRC if (last_seqnum == -1 || ssrc_current != adu->getSource().getID()) { if (recvd_codec != CODEC_NULL) { ssrc_current = adu->getSource().getID(); // An SSRC defines a sequence number space. So a new // SSRC starts with a new random sequence number last_seqnum = -1; log_file->write_header("t_audio_tx::run", LOG_NORMAL); log_file->write_raw("Audio tx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": play SSRC "); log_file->write_raw(ssrc_current); log_file->write_endl(); log_file->write_footer(); } else { // SSRC received had an unsupported codec // Discard. // KLUDGE: for now this supports a scenario where a // far-end starts ZRTP negotiation by sending CN // packets with a separate SSRC while ZRTP is disabled // in Twinkle. Twinkle will then receive the CN packets // and discard them here as CN is an unsupported codec. log_file->write_header("t_audio_tx::run", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Audio tx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": SSRC received ("); log_file->write_raw(adu->getSource().getID()); log_file->write_raw(") has unsupported codec "); log_file->write_raw(adu->getType()); log_file->write_endl(); log_file->write_footer(); MEMMAN_DELETE(const_cast(adu)); delete adu; continue; } } map::const_iterator it_decoder; it_decoder = map_audio_decoder.find(recvd_codec); if (it_decoder != map_audio_decoder.end()) { if (codec != recvd_codec) { codec = recvd_codec; get_line()->ci_set_recv_codec(codec); ui->cb_async_recv_codec_changed(get_line()->get_line_number(), codec); log_file->write_header("t_audio_tx::run", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Audio tx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": codec change to "); log_file->write_raw(ui->format_codec(codec)); log_file->write_endl(); log_file->write_footer(); } } else { if (adu->getType() == pt_telephone_event || adu->getType() == pt_telephone_event_alt) { recvd_dtmf = true; } else { if (codec != CODEC_UNSUPPORTED) { codec = CODEC_UNSUPPORTED; get_line()->ci_set_recv_codec(codec); ui->cb_async_recv_codec_changed( get_line()->get_line_number(), codec); log_file->write_header("t_audio_tx::run", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Audio tx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": payload type "); log_file->write_raw(adu->getType()); log_file->write_raw(" not supported\n"); log_file->write_footer(); } last_seqnum = adu->getSeqNum(); MEMMAN_DELETE(const_cast(adu)); delete adu; continue; } } // DTMF event if (recvd_dtmf) { // NOTE: the DTMF tone will be detected here // while there might still be data in the jitter // buffer. If the jitter buffer was already sent // to the DSP, then the DSP will continue to play // out the buffer sound samples. if (dtmf_previous_timestamp != rtp_timestamp) { // A new DTMF tone has been received. dtmf_previous_timestamp = rtp_timestamp; t_rtp_telephone_event *e = (t_rtp_telephone_event *)adu->getData(); ui->cb_async_dtmf_detected(get_line()->get_line_number(), e->get_event()); // Log DTMF event log_file->write_header("t_audio_tx::run"); log_file->write_raw("Audio tx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": detected DTMF event - "); log_file->write_raw(e->get_event()); log_file->write_endl(); log_file->write_footer(); } recvd_dtmf = false; last_seqnum = adu->getSeqNum(); MEMMAN_DELETE(const_cast(adu)); delete adu; continue; } // Discard invalide payload sizes if (!map_audio_decoder[codec]->valid_payload_size( adu->getSize(), SAMPLE_BUF_SIZE / 2)) { log_file->write_header("t_audio_tx::run", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Audio tx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": RTP payload size ("); log_file->write_raw((unsigned long)(adu->getSize())); log_file->write_raw(" bytes) invalid for \n"); log_file->write_raw(ui->format_codec(codec)); log_file->write_footer(); last_seqnum = adu->getSeqNum(); MEMMAN_DELETE(const_cast(adu)); delete adu; continue; } unsigned short recvd_ptime; recvd_ptime = map_audio_decoder[codec]->get_ptime(adu->getSize()); // Log a change of ptime if (ptime != recvd_ptime) { log_file->write_header("t_audio_tx::run", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Audio tx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": ptime changed from "); log_file->write_raw(ptime); log_file->write_raw(" ms to "); log_file->write_raw(recvd_ptime); log_file->write_raw(" ms\n"); log_file->write_footer(); ptime = recvd_ptime; } // Check for lost packets // This must be done before decoding the received samples as the // speex decoder has its own PLC algorithm for which it needs the decoding // state before decoding the new samples. seq16_t seq_recvd(adu->getSeqNum()); seq16_t seq_last(static_cast(last_seqnum)); if (last_seqnum != -1 && seq_recvd - seq_last > 1) { // Packets have been lost uint16 num_lost = (seq_recvd - seq_last) - 1; log_file->write_header("t_audio_tx::run", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Audio tx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": "); log_file->write_raw(num_lost); log_file->write_raw(" RTP packets lost.\n"); log_file->write_footer(); if (num_lost <= conceal_num) { // Conceal packet loss conceal(num_lost); } clear_conceal_buf(); } // Determine if resampling is needed due to dynamic change to // codec with other sample rate. short downsample_factor = 1; short upsample_factor = 1; if (audio_sample_rate(codec) > sc_sample_rate) { downsample_factor = audio_sample_rate(codec) / sc_sample_rate; } else if (audio_sample_rate(codec) < sc_sample_rate) { upsample_factor = sc_sample_rate / audio_sample_rate(codec); } // Create sample buffer. If no resampling is needed, the sample // buffer from the audio_tx object can be used directly. // Otherwise a temporary sample buffers is created that will // be resampled to the object's sample buffer later. short *sb; int sb_size; if (downsample_factor > 1) { sb_size = SAMPLE_BUF_SIZE / 2 * downsample_factor; sb = new short[sb_size]; MEMMAN_NEW_ARRAY(sb); } else if (upsample_factor > 1) { sb_size = SAMPLE_BUF_SIZE / 2; sb = new short[SAMPLE_BUF_SIZE / 2]; MEMMAN_NEW_ARRAY(sb); } else { sb_size = SAMPLE_BUF_SIZE / 2; sb = (short *)sample_buf; } // Decode the audio unsigned char *payload = const_cast(adu->getData()); short sample_size; // size in bytes sample_size = 2 * map_audio_decoder[codec]->decode(payload, adu->getSize(), sb, sb_size); // Resample if needed if (downsample_factor > 1) { short *p = sb; sb = (short *)sample_buf; for (int i = 0; i < sample_size / 2; i += downsample_factor) { sb[i / downsample_factor] = p[i]; } MEMMAN_DELETE_ARRAY(p); delete [] p; sample_size /= downsample_factor; } else if (upsample_factor > 1) { short *p = sb; sb = (short *)sample_buf; for (int i = 0; i < sample_size / 2; i++) { for (int j = 0; j < upsample_factor; j++) { sb[i * upsample_factor + j] = p[i]; } } MEMMAN_DELETE_ARRAY(p); delete [] p; sample_size *= upsample_factor; } // If the decoder deliverd 0 bytes, then it failed if (sample_size == 0) { last_seqnum = adu->getSeqNum(); MEMMAN_DELETE(const_cast(adu)); delete adu; continue; } // Discard packet if we are lacking behind. This happens if the // soundcard plays at a rate less than the requested sample rate. if (rtp_session->isWaiting(&(adu->getSource()))) { uint32 last_ts = rtp_session->getLastTimestamp(&(adu->getSource())); uint32 diff; diff = last_ts - rtp_timestamp; if (diff > (uint32_t)(JITTER_BUF_SIZE(sc_sample_rate) / AUDIO_SAMPLE_SIZE) * 8) { log_file->write_header("t_audio_tx::run", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Audio tx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": discard delayed packet.\n"); log_file->write_raw("Timestamp: "); log_file->write_raw(rtp_timestamp); log_file->write_raw(", Last timestamp: "); log_file->write_raw((long unsigned int)last_ts); log_file->write_endl(); log_file->write_footer(); last_seqnum = adu->getSeqNum(); MEMMAN_DELETE(const_cast(adu)); delete adu; continue; } } play_pcm(sample_buf, sample_size); retain_for_concealment(sample_buf, sample_size); last_seqnum = adu->getSeqNum(); MEMMAN_DELETE(const_cast(adu)); delete adu; // No sleep is done here but in the loop waiting // for a new packet. If a packet is already available // it can be send to the sound card immediately so // the play-out buffer keeps filled. // If the play-out buffer gets empty you hear a // crack in the sound. #ifdef HAVE_SPEEX // store decoded output for (optional) echo cancellation if (audio_session->get_do_echo_cancellation()) { if (audio_session->get_echo_captured_last()) { speex_echo_playback(audio_session->get_speex_echo_state(), (spx_int16_t *) sb); audio_session->set_echo_captured_last(false);; } } #endif } phone->remove_prohibited_thread(); ui->remove_prohibited_thread(); is_running = false; } void t_audio_tx::set_pt_telephone_event(int pt, int pt_alt) { pt_telephone_event = pt; pt_telephone_event_alt = pt_alt; } t_line *t_audio_tx::get_line(void) const { return audio_session->get_line(); } void t_audio_tx::join_3way(bool mixer, t_audio_tx *peer_tx, t_audio_rx *peer_rx) { mtx_3way.lock(); if (is_3way) { log_file->write_header("t_audio_tx::join_3way"); log_file->write_raw("ERROR: audio tx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(" - 3way is already active.\n"); log_file->write_footer(); mtx_3way.unlock(); return; } // Logging log_file->write_header("t_audio_tx::join_3way"); log_file->write_raw("Audio tx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": join 3-way.\n"); if (mixer) { log_file->write_raw("Role is: mixer.\n"); } else { log_file->write_raw("Role is: non-mixing.\n"); } if (peer_tx) { log_file->write_raw("A peer transmitter already exists.\n"); } else { log_file->write_raw("A peer transmitter does not exist.\n"); } if (peer_rx) { log_file->write_raw("A peer receiver already exists.\n"); } else { log_file->write_raw("A peer receiver does not exist.\n"); } log_file->write_footer(); peer_tx_3way = peer_tx; peer_rx_3way = peer_rx; is_3way_mixer = mixer; is_3way = true; // Create buffers for mixing mix_buf_3way = new unsigned char[SAMPLE_BUF_SIZE]; MEMMAN_NEW_ARRAY(mix_buf_3way); // See comments in audio_rx.cpp for the size of this buffer. media_3way_peer_tx = new t_media_buffer(JITTER_BUF_SIZE(sc_sample_rate)); MEMMAN_NEW(media_3way_peer_tx); mtx_3way.unlock(); } void t_audio_tx::set_peer_tx_3way(t_audio_tx *peer_tx) { mtx_3way.lock(); if (!is_3way) { mtx_3way.unlock(); return; } // Logging log_file->write_header("t_audio_tx::set_peer_tx_3way"); log_file->write_raw("Audio tx line "); log_file->write_raw(get_line()->get_line_number()+1); if (peer_tx) { log_file->write_raw(": set peer transmitter.\n"); } else { log_file->write_raw(": erase peer transmitter.\n"); } if (is_3way_mixer) { log_file->write_raw("Role is: mixer.\n"); } else { log_file->write_raw("Role is: non-mixing.\n"); } log_file->write_footer(); peer_tx_3way = peer_tx; mtx_3way.unlock(); } void t_audio_tx::set_peer_rx_3way(t_audio_rx *peer_rx) { mtx_3way.lock(); if (!is_3way) { mtx_3way.unlock(); return; } // Logging log_file->write_header("t_audio_tx::set_peer_rx_3way"); log_file->write_raw("Audio tx line "); log_file->write_raw(get_line()->get_line_number()+1); if (peer_rx) { log_file->write_raw(": set peer receiver.\n"); } else { log_file->write_raw(": erase peer receiver.\n"); } if (is_3way_mixer) { log_file->write_raw("Role is: mixer.\n"); } else { log_file->write_raw("Role is: non-mixing.\n"); } log_file->write_footer(); peer_rx_3way = peer_rx; mtx_3way.unlock(); } void t_audio_tx::set_mixer_3way(bool mixer) { mtx_3way.lock(); if (!is_3way) { mtx_3way.unlock(); return; } // Logging log_file->write_header("t_audio_tx::set_mixer_3way"); log_file->write_raw("Audio tx line "); log_file->write_raw(get_line()->get_line_number()+1); if (mixer) { log_file->write_raw(": change role to: mixer.\n"); } else { log_file->write_raw(": change role to: non-mixing.\n"); } log_file->write_footer(); is_3way_mixer = mixer; mtx_3way.unlock(); } void t_audio_tx::stop_3way(void) { mtx_3way.lock(); if (!is_3way) { log_file->write_header("t_audio_tx::stop_3way"); log_file->write_raw("ERROR: audio tx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(" - 3way is not active.\n"); log_file->write_footer(); mtx_3way.unlock(); return; } // Logging log_file->write_header("t_audio_tx::stop_3way"); log_file->write_raw("Audio tx line "); log_file->write_raw(get_line()->get_line_number()+1); log_file->write_raw(": stop 3-way.\n"); log_file->write_footer(); is_3way = false; is_3way_mixer = false; if (media_3way_peer_tx) { MEMMAN_DELETE(media_3way_peer_tx); delete media_3way_peer_tx; media_3way_peer_tx = NULL; } if (mix_buf_3way) { MEMMAN_DELETE_ARRAY(mix_buf_3way); delete [] mix_buf_3way; mix_buf_3way = NULL; } mtx_3way.unlock(); } void t_audio_tx::post_media_peer_tx_3way(unsigned char *media, int len, unsigned short peer_sample_rate) { mtx_3way.lock(); if (!is_3way || !is_3way_mixer) { mtx_3way.unlock(); return; } if (peer_sample_rate != sc_sample_rate) { // Resample media from peer to sample rate of this transmitter int output_len = (len / 2) * sc_sample_rate / peer_sample_rate; short *output_buf = new short[output_len]; MEMMAN_NEW_ARRAY(output_buf); int resample_len = resample((short *)media, len / 2, peer_sample_rate, output_buf, output_len, sc_sample_rate); media_3way_peer_tx->add((unsigned char *)output_buf, resample_len * 2); MEMMAN_DELETE_ARRAY(output_buf); delete [] output_buf; } else { media_3way_peer_tx->add(media, len); } mtx_3way.unlock(); } bool t_audio_tx::get_is_3way_mixer(void) const { return is_3way_mixer; } twinkle-1.10.1/src/audio/audio_tx.h000066400000000000000000000151101277565361200171400ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _AUDIO_TX_H #define _AUDIO_TX_H // Receive RTP and send audio to soundcard #include #include #include "audio_codecs.h" #include "audio_decoder.h" #include "audio_rx.h" #include "media_buffer.h" #include "rtp_telephone_event.h" #include "user.h" #include "threads/mutex.h" #include "audio_device.h" #include "twinkle_rtp_session.h" #include "twinkle_config.h" #ifdef HAVE_GSM #include #else #include "gsm/inc/gsm.h" #endif using namespace std; using namespace ost; // Forward declarations class t_audio_session; class t_line; class t_audio_tx { private: // audio_session owning this audio transmitter t_audio_session *audio_session; // User profile of user using the line // This is a pointer to the user_config owned by a phone user. // So this pointer should never be deleted. t_user *user_config; // file descriptor audio capture device t_audio_io *playback_device; t_twinkle_rtp_session *rtp_session; // Indicates if this transmitter is part of a 3-way conference bool is_3way; // Indicates if this transmitter is the mixer in a 3-way conference. // The mixer will mix this audio stream with the audio from the other // party and send it to the soundcard. // If the transmitter is part of a 3-way conference, but not the // mixer, then it simply has to send its audio payload to the mixer. bool is_3way_mixer; // Media buffer for media from the other far-end. This buffer is // used by the mixer. t_media_buffer *media_3way_peer_tx; // The peer audio transmitter in a 3-way conference t_audio_tx *peer_tx_3way; // The audio receiver that needs input from this transmitter in // a 3-way conference. t_audio_rx *peer_rx_3way; // Buffer for mixing purposes in 3-way conference. unsigned char *mix_buf_3way; // Mutex to protect 3-way resources t_mutex mtx_3way; // Codec information t_audio_codec codec; map payload2codec; unsigned short ptime; // in milliseconds // Sample rate of sound card. // The sample rate of the sound card is set to the sample rate // used for the initial codec. The far end may dynamically switch // to a codec with another sample rate. This will not change the // sample rate of the sound card! (capture and playback cannot // be done at different sampling rates). unsigned short sc_sample_rate; // Mapping from codecs to decoders map map_audio_decoder; // Buffer to store PCM samples of a received RTP packet unsigned char *sample_buf; // Jitter buffer (PCM). // jitter_buf_len indicates the number of bytes in the jitter buffer. // At the start of playing the samples are stored in the jitter buffer. // Once the buffer is full, all samples are copied to the memory of // the soundcard. From that point the soundcard itself is the jitter // buffer. unsigned char *jitter_buf; unsigned short jitter_buf_len; bool load_jitter_buf; // Buffer to keep last played packets for concealment. unsigned char *conceal_buf[MAX_CONCEALMENT]; unsigned short conceal_buflen[MAX_CONCEALMENT]; // length of packet short conceal_pos; // points to the oldest packet. short conceal_num; // number of retained packets. unsigned short soundcard_buf_size; // Payload type for telephone-event payload. // Some endpoints ignore the payload type that was sent in an // outgoing INVITE and simply sends it with the payload type, // they indicated in the 200 OK. Accept both payloads for // interoperability. int pt_telephone_event; int pt_telephone_event_alt; // Timestamp of previous DTMF tone unsigned long dtmf_previous_timestamp; // Inidicates if the playing thread is running volatile bool is_running; // The thread exits when this indicator is set to true volatile bool stop_running; // Retain a packet (PCM encoded) for possible concealment. void retain_for_concealment(unsigned char *buf, unsigned short len); // Play last num packets again. void conceal(short num); // Erase concealment buffers. void clear_conceal_buf(void); // Play PCM encoded samples // - only_3rd_party indicates if there is only 3rd_party audio available, // i.e. due to jitter, packet loss or silence suppression void play_pcm(unsigned char *buf, unsigned short len, bool only_3rd_party = false); public: // Create the audio transmitter // _fd file descriptor of capture device // _rtp_session RTP socket tp send the RTP stream // _codec audio codec to use // _ptime length of the audio packets in ms // _ptime = 0 means use default ptime value for the codec t_audio_tx(t_audio_session *_audio_session, t_audio_io *_playback_device, t_twinkle_rtp_session *_rtp_session, t_audio_codec _codec, const map &_payload2codec, unsigned short _ptime = 0); ~t_audio_tx(); // Set the is running flag void set_running(bool running); void run(void); // Set the dynamic payload type for telephone events void set_pt_telephone_event(int pt, int pt_alt); // Get phone line belonging to this audio transmitter t_line *get_line(void) const; // Join this transmitter in a 3way conference. // mixer indicates if this transmitter must be the mixer. // - peer_tx is the peer transmitter in a 3-way // - audio_rx is the audio receiver needing the output from this // transmitter for mixing. void join_3way(bool mixer, t_audio_tx *peer_tx, t_audio_rx *peer_rx); // Change the peer rx/tx (NULL to erase) void set_peer_tx_3way(t_audio_tx *peer_tx); void set_peer_rx_3way(t_audio_rx *peer_rx); // Change the mixer role void set_mixer_3way(bool mixer); // Stop the 3-way conference and make it a 1-on-1 call again. void stop_3way(void); // Post media from the peer transmitter for a 3-way mixer. void post_media_peer_tx_3way(unsigned char *media, int len, unsigned short peer_sample_rate); // Returns if this transmitter is the mixer in a 3-way bool get_is_3way_mixer(void) const; }; #endif twinkle-1.10.1/src/audio/dtmf_player.cpp000066400000000000000000000131251277565361200201710ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include "dtmf_player.h" #include "audio_rx.h" #include "line.h" #include "rtp_telephone_event.h" #include "log.h" ///////////////////////////////////////// // class t_dtmf_player ///////////////////////////////////////// t_dtmf_player::t_dtmf_player(t_audio_rx *audio_rx, t_audio_encoder *audio_encoder, t_user *user_config, t_dtmf_ev dtmf_tone, uint32 dtmf_timestamp, uint16 nsamples) : _audio_rx(audio_rx), _user_config(user_config), _audio_encoder(audio_encoder), _dtmf_pause(false), _dtmf_stop(false), _dtmf_current(dtmf_tone), _dtmf_timestamp(dtmf_timestamp), _dtmf_duration(0), _nsamples(nsamples) {} uint32 t_dtmf_player::get_timestamp(void) { return _dtmf_timestamp; } bool t_dtmf_player::finished(void) { return _dtmf_stop; } ///////////////////////////////////////// // class t_rtp_event_dtmf_player ///////////////////////////////////////// t_rtp_event_dtmf_player::t_rtp_event_dtmf_player(t_audio_rx *audio_rx, t_audio_encoder *audio_encoder, t_user *user_config, t_dtmf_ev dtmf_tone, uint32 dtmf_timestamp, uint16 nsamples) : t_dtmf_player(audio_rx, audio_encoder, user_config, dtmf_tone, dtmf_timestamp, nsamples) { } uint16 t_rtp_event_dtmf_player::get_payload(uint8 *payload, uint16 payload_size, uint32 timestamp, uint32 &rtp_timestamp) { t_rtp_telephone_event *dtmf_payload = (t_rtp_telephone_event *)payload; assert(sizeof(t_rtp_telephone_event) <= payload_size); // RFC 2833 3.5, 3.6 dtmf_payload->set_event(_dtmf_current); dtmf_payload->set_reserved(false); dtmf_payload->set_volume(_user_config->get_dtmf_volume()); if (_dtmf_pause) { // Trailing pause phase of a DTMF tone // Repeat the last packet dtmf_payload->set_end(true); int pause_duration = timestamp - _dtmf_timestamp - _dtmf_duration + _nsamples; if (pause_duration / _nsamples * _audio_encoder->get_ptime() >= _user_config->get_dtmf_pause()) { // This is the last packet to be sent for the // current DTMF tone. _dtmf_stop = true; log_file->write_header("t_rtp_event_dtmf_player::get_payload", LOG_NORMAL); log_file->write_raw("Audio rx line "); log_file->write_raw(_audio_rx->get_line()->get_line_number()+1); log_file->write_raw(": finish DTMF event - "); log_file->write_raw(_dtmf_current); log_file->write_endl(); log_file->write_footer(); } } else { // Play phase of a DTMF tone // The duration counts from the start of the tone. _dtmf_duration = timestamp - _dtmf_timestamp + _nsamples; // Check if the tone must end if (_dtmf_duration / _nsamples * _audio_encoder->get_ptime() >= _user_config->get_dtmf_duration()) { dtmf_payload->set_end(true); _dtmf_pause = true; } else { dtmf_payload->set_end(false); } } dtmf_payload->set_duration(_dtmf_duration); rtp_timestamp = _dtmf_timestamp; return sizeof(t_rtp_telephone_event); } ///////////////////////////////////////// // class t_inband_dtmf_player ///////////////////////////////////////// t_inband_dtmf_player::t_inband_dtmf_player(t_audio_rx *audio_rx, t_audio_encoder *audio_encoder, t_user *user_config, t_dtmf_ev dtmf_tone, uint32 dtmf_timestamp, uint16 nsamples) : t_dtmf_player(audio_rx, audio_encoder, user_config, dtmf_tone, dtmf_timestamp, nsamples), _freq_gen(dtmf_tone, -(user_config->get_dtmf_volume())) { } uint16 t_inband_dtmf_player::get_payload(uint8 *payload, uint16 payload_size, uint32 timestamp, uint32 &rtp_timestamp) { int16 sample_buf[_nsamples]; if (_dtmf_pause) { int pause_duration = timestamp - _dtmf_timestamp - _dtmf_duration + _nsamples; memset(sample_buf, 0, _nsamples * 2); if (pause_duration / _nsamples * _audio_encoder->get_ptime() >= _user_config->get_dtmf_pause()) { // This is the last packet to be sent for the // current DTMF tone. _dtmf_stop = true; log_file->write_header("t_inband_dtmf_player::get_payload", LOG_NORMAL); log_file->write_raw("Audio rx line "); log_file->write_raw(_audio_rx->get_line()->get_line_number()+1); log_file->write_raw(": finish DTMF event - "); log_file->write_raw(_dtmf_current); log_file->write_endl(); log_file->write_footer(); } } else { // Timestamp and interval for _freq_gen must be in usec uint32 ts_start = (timestamp - _dtmf_timestamp) * 1000000 / _audio_encoder->get_sample_rate(); _freq_gen.get_samples(sample_buf, _nsamples, ts_start, 1000000.0 / _audio_encoder->get_sample_rate()); // The duration counts from the start of the tone. _dtmf_duration = timestamp - _dtmf_timestamp + _nsamples; // Check if the tone must end if (_dtmf_duration / _nsamples * _audio_encoder->get_ptime() >= _user_config->get_dtmf_duration()) { _dtmf_pause = true; } } // Encode audio samples bool silence; rtp_timestamp = timestamp; return _audio_encoder->encode(sample_buf, _nsamples, payload, payload_size, silence); } twinkle-1.10.1/src/audio/dtmf_player.h000066400000000000000000000057241277565361200176440ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Classes to generate RTP payloads for DTMF tones. #ifndef _DTMF_PLAYER_H #define _DTMF_PLAYER_H #include "twinkle_config.h" #include "audio_encoder.h" #include "freq_gen.h" #include "user.h" // Forward declarations class t_audio_rx; // Abstract class defintion for DTMF player class t_dtmf_player { protected: // Audio receiver owning the DTMF player. t_audio_rx *_audio_rx; t_user *_user_config; t_audio_encoder *_audio_encoder; // Settings for current DTMF tone bool _dtmf_pause; // indicates if playing is in the pause phase bool _dtmf_stop; // indicates if DTMF should be stopped t_dtmf_ev _dtmf_current; // Currently played DTMF tone uint32 _dtmf_timestamp; // RTP timestamp (start of tone) uint16 _dtmf_duration; // Duration of the tone currently played uint16 _nsamples; // number of samples taken per packet public: t_dtmf_player(t_audio_rx *audio_rx, t_audio_encoder *audio_encoder, t_user *user_config, t_dtmf_ev dtmf_tone, uint32 dtmf_timestamp, uint16 nsamples); virtual ~t_dtmf_player() {}; // Get payload for the DTMF tone // rtp_timestamp will be set with the timestamp value to be put in the // RTP header // Returns the payload size virtual uint16 get_payload(uint8 *payload, uint16 payload_size, uint32 timestamp, uint32 &rtp_timestamp) = 0; uint32 get_timestamp(void); // Returns true when last payload has been delivered. bool finished(void); }; // DTMF player for RFC 2833 RTP telephone events class t_rtp_event_dtmf_player : public t_dtmf_player { public: t_rtp_event_dtmf_player(t_audio_rx *audio_rx, t_audio_encoder *audio_encoder, t_user *user_config, t_dtmf_ev dtmf_tone, uint32 dtmf_timestamp, uint16 nsamples); virtual uint16 get_payload(uint8 *payload, uint16 payload_size, uint32 timestamp, uint32 &rtp_timestamp); }; // DTMF player for inband tones class t_inband_dtmf_player : public t_dtmf_player { private: // Frequency generator to generate the inband tones. t_freq_gen _freq_gen; public: t_inband_dtmf_player(t_audio_rx *audio_rx, t_audio_encoder *audio_encoder, t_user *user_config, t_dtmf_ev dtmf_tone, uint32 dtmf_timestamp, uint16 nsamples); virtual uint16 get_payload(uint8 *payload, uint16 payload_size, uint32 timestamp, uint32 &rtp_timestamp); }; #endif twinkle-1.10.1/src/audio/freq_gen.cpp000066400000000000000000000064541277565361200174600ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "freq_gen.h" #include #include #include #include "rtp_telephone_event.h" #define PI 3.141592653589793 t_freq_gen::t_freq_gen(vector frequencies, int8 db_level) { assert(frequencies.size() > 0); assert(db_level <= 0); _frequencies = frequencies; // dB = 20 * log(amplitude / 32768) // 32767 is used below as +32768 does not fit in 16 bits _amplitude = int16(pow(10.0, db_level / 20.0) * 32767 / _frequencies.size()); } t_freq_gen::t_freq_gen(t_dtmf_ev dtmf, int8 db_level) : _frequencies(2) { assert(db_level <= 0); switch (dtmf) { case TEL_EV_DTMF_1: _frequencies[0] = 697; _frequencies[1] = 1209; break; case TEL_EV_DTMF_2: _frequencies[0] = 697; _frequencies[1] = 1336; break; case TEL_EV_DTMF_3: _frequencies[0] = 697; _frequencies[1] = 1477; break; case TEL_EV_DTMF_A: _frequencies[0] = 697; _frequencies[1] = 1633; break; case TEL_EV_DTMF_4: _frequencies[0] = 770; _frequencies[1] = 1209; break; case TEL_EV_DTMF_5: _frequencies[0] = 770; _frequencies[1] = 1336; break; case TEL_EV_DTMF_6: _frequencies[0] = 770; _frequencies[1] = 1477; break; case TEL_EV_DTMF_B: _frequencies[0] = 770; _frequencies[1] = 1633; break; case TEL_EV_DTMF_7: _frequencies[0] = 852; _frequencies[1] = 1209; break; case TEL_EV_DTMF_8: _frequencies[0] = 852; _frequencies[1] = 1336; break; case TEL_EV_DTMF_9: _frequencies[0] = 852; _frequencies[1] = 1477; break; case TEL_EV_DTMF_C: _frequencies[0] = 852; _frequencies[1] = 1633; break; case TEL_EV_DTMF_STAR: _frequencies[0] = 941; _frequencies[1] = 1209; break; case TEL_EV_DTMF_0: _frequencies[0] = 941; _frequencies[1] = 1336; break; case TEL_EV_DTMF_POUND: _frequencies[0] = 941; _frequencies[1] = 1477; break; case TEL_EV_DTMF_D: _frequencies[0] = 941; _frequencies[1] = 1633; break; default: assert(false); _frequencies.clear(); } // dB = 20 * log(amplitude / 32768) // 32767 is used below as +32768 does not fit in 16 bits _amplitude = int16(pow(10.0, db_level / 20.0) * 32767 / _frequencies.size()); } int16 t_freq_gen::get_sample(uint32 ts_usec) const { double freq_sum = 0.0; for (vector::const_iterator f = _frequencies.begin(); f != _frequencies.end(); f++) { freq_sum += sin(*f * 2.0 * PI * (double)ts_usec / 1000000.0); } return (int16)(_amplitude * freq_sum); } void t_freq_gen::get_samples(int16 *sample_buf, uint16 buf_len, uint32 ts_start, double interval) const { for (uint16 i = 0; i < buf_len; i++) { sample_buf[i] = get_sample(uint32(ts_start + interval * i)); } } twinkle-1.10.1/src/audio/freq_gen.h000066400000000000000000000026401277565361200171160ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Frequency generator // // This file contains definitions of the frequency generator. // The frequency generator generates tones build from a list of // frequencies. #ifndef _FREQ_GEN_H #define _FREQ_GEN_H #include #include #include "rtp_telephone_event.h" using namespace std; class t_freq_gen { private: vector _frequencies; int16 _amplitude; public: t_freq_gen(vector frequencies, int8 db_level); t_freq_gen(t_dtmf_ev dtmf, int8 db_level); // Get sound sample on a particular timestamp in us. int16 get_sample(uint32 ts_usec) const; void get_samples(int16 *sample_buf, uint16 buf_len, uint32 ts_start, double interval) const; }; #endif twinkle-1.10.1/src/audio/g711.cpp000066400000000000000000000202511277565361200163400ustar00rootroot00000000000000/* * This source code is a product of Sun Microsystems, Inc. and is provided * for unrestricted use. Users may copy or modify this source code without * charge. * * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. * * Sun source code is provided with no support and without any obligation on * the part of Sun Microsystems, Inc. to assist in its use, correction, * modification or enhancement. * * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE * OR ANY PART THEREOF. * * In no event will Sun Microsystems, Inc. be liable for any lost revenue * or profits or other special, indirect and consequential damages, even if * Sun has been advised of the possibility of such damages. * * Sun Microsystems, Inc. * 2550 Garcia Avenue * Mountain View, California 94043 */ /* * g711.c * * u-law, A-law and linear PCM conversions. */ /* * December 30, 1994: * Functions linear2alaw, linear2ulaw have been updated to correctly * convert unquantized 16 bit values. * Tables for direct u- to A-law and A- to u-law conversions have been * corrected. * Borge Lindberg, Center for PersonKommunikation, Aalborg University. * bli@cpk.auc.dk * */ #include "g711.h" #define SIGN_BIT (0x80) /* Sign bit for a A-law byte. */ #define QUANT_MASK (0xf) /* Quantization field mask. */ #define NSEGS (8) /* Number of A-law segments. */ #define SEG_SHIFT (4) /* Left shift for segment number. */ #define SEG_MASK (0x70) /* Segment field mask. */ static short seg_aend[8] = {0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF}; static short seg_uend[8] = {0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF}; /* copy from CCITT G.711 specifications */ unsigned char _u2a[128] = { /* u- to A-law conversions */ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 27, 29, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, /* corrected: 81, 82, 83, 84, 85, 86, 87, 88, should be: */ 80, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128}; unsigned char _a2u[128] = { /* A- to u-law conversions */ 1, 3, 5, 7, 9, 11, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 32, 33, 33, 34, 34, 35, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 48, 49, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 65, 66, 67, 68, 69, 70, 71, 72, /* corrected: 73, 74, 75, 76, 77, 78, 79, 79, should be: */ 73, 74, 75, 76, 77, 78, 79, 80, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127}; static short search( short val, short *table, short size) { short i; for (i = 0; i < size; i++) { if (val <= *table++) return (i); } return (size); } /* * linear2alaw() - Convert a 16-bit linear PCM value to 8-bit A-law * * linear2alaw() accepts an 16-bit integer and encodes it as A-law data. * * Linear Input Code Compressed Code * ------------------------ --------------- * 0000000wxyza 000wxyz * 0000001wxyza 001wxyz * 000001wxyzab 010wxyz * 00001wxyzabc 011wxyz * 0001wxyzabcd 100wxyz * 001wxyzabcde 101wxyz * 01wxyzabcdef 110wxyz * 1wxyzabcdefg 111wxyz * * For further information see John C. Bellamy's Digital Telephony, 1982, * John Wiley & Sons, pps 98-111 and 472-476. */ unsigned char linear2alaw( short pcm_val) /* 2's complement (16-bit range) */ { short mask; short seg; unsigned char aval; pcm_val = pcm_val >> 3; if (pcm_val >= 0) { mask = 0xD5; /* sign (7th) bit = 1 */ } else { mask = 0x55; /* sign bit = 0 */ pcm_val = -pcm_val - 1; } /* Convert the scaled magnitude to segment number. */ seg = search(pcm_val, seg_aend, 8); /* Combine the sign, segment, and quantization bits. */ if (seg >= 8) /* out of range, return maximum value. */ return (unsigned char) (0x7F ^ mask); else { aval = (unsigned char) seg << SEG_SHIFT; if (seg < 2) aval |= (pcm_val >> 1) & QUANT_MASK; else aval |= (pcm_val >> seg) & QUANT_MASK; return (aval ^ mask); } } /* * alaw2linear() - Convert an A-law value to 16-bit linear PCM * */ short alaw2linear( unsigned char a_val) { short t; short seg; a_val ^= 0x55; t = (a_val & QUANT_MASK) << 4; seg = ((unsigned)a_val & SEG_MASK) >> SEG_SHIFT; switch (seg) { case 0: t += 8; break; case 1: t += 0x108; break; default: t += 0x108; t <<= seg - 1; } return ((a_val & SIGN_BIT) ? t : -t); } #define BIAS (0x84) /* Bias for linear code. */ #define CLIP 8159 /* * linear2ulaw() - Convert a linear PCM value to u-law * * In order to simplify the encoding process, the original linear magnitude * is biased by adding 33 which shifts the encoding range from (0 - 8158) to * (33 - 8191). The result can be seen in the following encoding table: * * Biased Linear Input Code Compressed Code * ------------------------ --------------- * 00000001wxyza 000wxyz * 0000001wxyzab 001wxyz * 000001wxyzabc 010wxyz * 00001wxyzabcd 011wxyz * 0001wxyzabcde 100wxyz * 001wxyzabcdef 101wxyz * 01wxyzabcdefg 110wxyz * 1wxyzabcdefgh 111wxyz * * Each biased linear code has a leading 1 which identifies the segment * number. The value of the segment number is equal to 7 minus the number * of leading 0's. The quantization interval is directly available as the * four bits wxyz. * The trailing bits (a - h) are ignored. * * Ordinarily the complement of the resulting code word is used for * transmission, and so the code word is complemented before it is returned. * * For further information see John C. Bellamy's Digital Telephony, 1982, * John Wiley & Sons, pps 98-111 and 472-476. */ unsigned char linear2ulaw( short pcm_val) /* 2's complement (16-bit range) */ { short mask; short seg; unsigned char uval; /* Get the sign and the magnitude of the value. */ pcm_val = pcm_val >> 2; if (pcm_val < 0) { pcm_val = -pcm_val; mask = 0x7F; } else { mask = 0xFF; } if ( pcm_val > CLIP ) pcm_val = CLIP; /* clip the magnitude */ pcm_val += (BIAS >> 2); /* Convert the scaled magnitude to segment number. */ seg = search(pcm_val, seg_uend, 8); /* * Combine the sign, segment, quantization bits; * and complement the code word. */ if (seg >= 8) /* out of range, return maximum value. */ return (unsigned char) (0x7F ^ mask); else { uval = (unsigned char) (seg << 4) | ((pcm_val >> (seg + 1)) & 0xF); return (uval ^ mask); } } /* * ulaw2linear() - Convert a u-law value to 16-bit linear PCM * * First, a biased linear code is derived from the code word. An unbiased * output can then be obtained by subtracting 33 from the biased code. * * Note that this function expects to be passed the complement of the * original code word. This is in keeping with ISDN conventions. */ short ulaw2linear( unsigned char u_val) { short t; /* Complement to obtain normal u-law value. */ u_val = ~u_val; /* * Extract and bias the quantization bits. Then * shift up by the segment number and subtract out the bias. */ t = ((u_val & QUANT_MASK) << 3) + BIAS; t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT; return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS)); } /* A-law to u-law conversion */ unsigned char alaw2ulaw( unsigned char aval) { aval &= 0xff; return (unsigned char) ((aval & 0x80) ? (0xFF ^ _a2u[aval ^ 0xD5]) : (0x7F ^ _a2u[aval ^ 0x55])); } /* u-law to A-law conversion */ unsigned char ulaw2alaw( unsigned char uval) { uval &= 0xff; return (unsigned char) ((uval & 0x80) ? (0xD5 ^ (_u2a[0xFF ^ uval] - 1)) : (unsigned char) (0x55 ^ (_u2a[0x7F ^ uval] - 1))); } twinkle-1.10.1/src/audio/g711.h000066400000000000000000000006301277565361200160040ustar00rootroot00000000000000#ifndef _G711_H #define _G711_H // The linear PCM codes are signed 16 bit values // G.711 A-law unsigned char linear2alaw(short pcm_val); short alaw2linear(unsigned char a_val); // G.711 u-law unsigned char linear2ulaw(short pcm_val); short ulaw2linear(unsigned char u_val); // A-law <-> u-law conversions unsigned char alaw2ulaw(unsigned char aval); unsigned char ulaw2alaw(unsigned char uval); #endif twinkle-1.10.1/src/audio/g721.cpp000066400000000000000000000122331277565361200163420ustar00rootroot00000000000000/* * This source code is a product of Sun Microsystems, Inc. and is provided * for unrestricted use. Users may copy or modify this source code without * charge. * * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. * * Sun source code is provided with no support and without any obligation on * the part of Sun Microsystems, Inc. to assist in its use, correction, * modification or enhancement. * * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE * OR ANY PART THEREOF. * * In no event will Sun Microsystems, Inc. be liable for any lost revenue * or profits or other special, indirect and consequential damages, even if * Sun has been advised of the possibility of such damages. * * Sun Microsystems, Inc. * 2550 Garcia Avenue * Mountain View, California 94043 */ /* * g721.c * * Description: * * g721_encoder(), g721_decoder() * * These routines comprise an implementation of the CCITT G.721 ADPCM * coding algorithm. Essentially, this implementation is identical to * the bit level description except for a few deviations which * take advantage of work station attributes, such as hardware 2's * complement arithmetic and large memory. Specifically, certain time * consuming operations such as multiplications are replaced * with lookup tables and software 2's complement operations are * replaced with hardware 2's complement. * * The deviation from the bit level specification (lookup tables) * preserves the bit level performance specifications. * * As outlined in the G.721 Recommendation, the algorithm is broken * down into modules. Each section of code below is preceded by * the name of the module which it is implementing. * */ #include "g72x.h" #include "g711.h" static short qtab_721[7] = {-124, 80, 178, 246, 300, 349, 400}; /* * Maps G.721 code word to reconstructed scale factor normalized log * magnitude values. */ static short _dqlntab[16] = {-2048, 4, 135, 213, 273, 323, 373, 425, 425, 373, 323, 273, 213, 135, 4, -2048}; /* Maps G.721 code word to log of scale factor multiplier. */ static short _witab[16] = {-12, 18, 41, 64, 112, 198, 355, 1122, 1122, 355, 198, 112, 64, 41, 18, -12}; /* * Maps G.721 code words to a set of values whose long and short * term averages are computed and then compared to give an indication * how stationary (steady state) the signal is. */ static short _fitab[16] = {0, 0, 0, 0x200, 0x200, 0x200, 0x600, 0xE00, 0xE00, 0x600, 0x200, 0x200, 0x200, 0, 0, 0}; /* * g721_encoder() * * Encodes the input vale of linear PCM, A-law or u-law data sl and returns * the resulting code. -1 is returned for unknown input coding value. */ int g721_encoder( int sl, int in_coding, struct g72x_state *state_ptr) { short sezi, se, sez; /* ACCUM */ short d; /* SUBTA */ short sr; /* ADDB */ short y; /* MIX */ short dqsez; /* ADDC */ short dq, i; switch (in_coding) { /* linearize input sample to 14-bit PCM */ case AUDIO_ENCODING_ALAW: sl = alaw2linear(sl) >> 2; break; case AUDIO_ENCODING_ULAW: sl = ulaw2linear(sl) >> 2; break; case AUDIO_ENCODING_LINEAR: sl >>= 2; /* 14-bit dynamic range */ break; default: return (-1); } sezi = predictor_zero(state_ptr); sez = sezi >> 1; se = (sezi + predictor_pole(state_ptr)) >> 1; /* estimated signal */ d = sl - se; /* estimation difference */ /* quantize the prediction difference */ y = step_size(state_ptr); /* quantizer step size */ i = quantize(d, y, qtab_721, 7); /* i = ADPCM code */ dq = reconstruct(i & 8, _dqlntab[i], y); /* quantized est diff */ sr = (dq < 0) ? se - (dq & 0x3FFF) : se + dq; /* reconst. signal */ dqsez = sr + sez - se; /* pole prediction diff. */ update(4, y, _witab[i] << 5, _fitab[i], dq, sr, dqsez, state_ptr); return (i); } /* * g721_decoder() * * Description: * * Decodes a 4-bit code of G.721 encoded data of i and * returns the resulting linear PCM, A-law or u-law value. * return -1 for unknown out_coding value. */ int g721_decoder( int i, int out_coding, struct g72x_state *state_ptr) { short sezi, sei, sez, se; /* ACCUM */ short y; /* MIX */ short sr; /* ADDB */ short dq; short dqsez; i &= 0x0f; /* mask to get proper bits */ sezi = predictor_zero(state_ptr); sez = sezi >> 1; sei = sezi + predictor_pole(state_ptr); se = sei >> 1; /* se = estimated signal */ y = step_size(state_ptr); /* dynamic quantizer step size */ dq = reconstruct(i & 0x08, _dqlntab[i], y); /* quantized diff. */ sr = (dq < 0) ? (se - (dq & 0x3FFF)) : se + dq; /* reconst. signal */ dqsez = sr - se + sez; /* pole prediction diff. */ update(4, y, _witab[i] << 5, _fitab[i], dq, sr, dqsez, state_ptr); switch (out_coding) { case AUDIO_ENCODING_ALAW: return (tandem_adjust_alaw(sr, se, y, i, 8, qtab_721)); case AUDIO_ENCODING_ULAW: return (tandem_adjust_ulaw(sr, se, y, i, 8, qtab_721)); case AUDIO_ENCODING_LINEAR: return (sr << 2); /* sr was 14-bit dynamic range */ default: return (-1); } } twinkle-1.10.1/src/audio/g723_16.cpp000066400000000000000000000123771277565361200166630ustar00rootroot00000000000000/* * This source code is a product of Sun Microsystems, Inc. and is provided * for unrestricted use. Users may copy or modify this source code without * charge. * * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. * * Sun source code is provided with no support and without any obligation on * the part of Sun Microsystems, Inc. to assist in its use, correction, * modification or enhancement. * * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE * OR ANY PART THEREOF. * * In no event will Sun Microsystems, Inc. be liable for any lost revenue * or profits or other special, indirect and consequential damages, even if * Sun has been advised of the possibility of such damages. * * Sun Microsystems, Inc. * 2550 Garcia Avenue * Mountain View, California 94043 */ /* 16kbps version created, used 24kbps code and changing as little as possible. * G.726 specs are available from ITU's gopher or WWW site (http://www.itu.ch) * If any errors are found, please contact me at mrand@tamu.edu * -Marc Randolph */ /* * g723_16.c * * Description: * * g723_16_encoder(), g723_16_decoder() * * These routines comprise an implementation of the CCITT G.726 16 Kbps * ADPCM coding algorithm. Essentially, this implementation is identical to * the bit level description except for a few deviations which take advantage * of workstation attributes, such as hardware 2's complement arithmetic. * */ #include "g72x.h" #include "g711.h" /* * Maps G.723_16 code word to reconstructed scale factor normalized log * magnitude values. Comes from Table 11/G.726 */ static short _dqlntab[4] = { 116, 365, 365, 116}; /* Maps G.723_16 code word to log of scale factor multiplier. * * _witab[4] is actually {-22 , 439, 439, -22}, but FILTD wants it * as WI << 5 (multiplied by 32), so we'll do that here */ static short _witab[4] = {-704, 14048, 14048, -704}; /* * Maps G.723_16 code words to a set of values whose long and short * term averages are computed and then compared to give an indication * how stationary (steady state) the signal is. */ /* Comes from FUNCTF */ static short _fitab[4] = {0, 0xE00, 0xE00, 0}; /* Comes from quantizer decision level tables (Table 7/G.726) */ static short qtab_723_16[1] = {261}; /* * g723_16_encoder() * * Encodes a linear PCM, A-law or u-law input sample and returns its 2-bit code. * Returns -1 if invalid input coding value. */ int g723_16_encoder( int sl, int in_coding, struct g72x_state *state_ptr) { short sei, sezi, se, sez; /* ACCUM */ short d; /* SUBTA */ short y; /* MIX */ short sr; /* ADDB */ short dqsez; /* ADDC */ short dq, i; switch (in_coding) { /* linearize input sample to 14-bit PCM */ case AUDIO_ENCODING_ALAW: sl = alaw2linear(sl) >> 2; break; case AUDIO_ENCODING_ULAW: sl = ulaw2linear(sl) >> 2; break; case AUDIO_ENCODING_LINEAR: sl >>= 2; /* sl of 14-bit dynamic range */ break; default: return (-1); } sezi = predictor_zero(state_ptr); sez = sezi >> 1; sei = sezi + predictor_pole(state_ptr); se = sei >> 1; /* se = estimated signal */ d = sl - se; /* d = estimation diff. */ /* quantize prediction difference d */ y = step_size(state_ptr); /* quantizer step size */ i = quantize(d, y, qtab_723_16, 1); /* i = ADPCM code */ /* Since quantize() only produces a three level output * (1, 2, or 3), we must create the fourth one on our own */ if (i == 3) /* i code for the zero region */ if ((d & 0x8000) == 0) /* If d > 0, i=3 isn't right... */ i = 0; dq = reconstruct(i & 2, _dqlntab[i], y); /* quantized diff. */ sr = (dq < 0) ? se - (dq & 0x3FFF) : se + dq; /* reconstructed signal */ dqsez = sr + sez - se; /* pole prediction diff. */ update(2, y, _witab[i], _fitab[i], dq, sr, dqsez, state_ptr); return (i); } /* * g723_16_decoder() * * Decodes a 2-bit CCITT G.723_16 ADPCM code and returns * the resulting 16-bit linear PCM, A-law or u-law sample value. * -1 is returned if the output coding is unknown. */ int g723_16_decoder( int i, int out_coding, struct g72x_state *state_ptr) { short sezi, sei, sez, se; /* ACCUM */ short y; /* MIX */ short sr; /* ADDB */ short dq; short dqsez; i &= 0x03; /* mask to get proper bits */ sezi = predictor_zero(state_ptr); sez = sezi >> 1; sei = sezi + predictor_pole(state_ptr); se = sei >> 1; /* se = estimated signal */ y = step_size(state_ptr); /* adaptive quantizer step size */ dq = reconstruct(i & 0x02, _dqlntab[i], y); /* unquantize pred diff */ sr = (dq < 0) ? (se - (dq & 0x3FFF)) : (se + dq); /* reconst. signal */ dqsez = sr - se + sez; /* pole prediction diff. */ update(2, y, _witab[i], _fitab[i], dq, sr, dqsez, state_ptr); switch (out_coding) { case AUDIO_ENCODING_ALAW: return (tandem_adjust_alaw(sr, se, y, i, 2, qtab_723_16)); case AUDIO_ENCODING_ULAW: return (tandem_adjust_ulaw(sr, se, y, i, 2, qtab_723_16)); case AUDIO_ENCODING_LINEAR: return (sr << 2); /* sr was of 14-bit dynamic range */ default: return (-1); } } twinkle-1.10.1/src/audio/g723_24.cpp000066400000000000000000000110571277565361200166540ustar00rootroot00000000000000/* * This source code is a product of Sun Microsystems, Inc. and is provided * for unrestricted use. Users may copy or modify this source code without * charge. * * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. * * Sun source code is provided with no support and without any obligation on * the part of Sun Microsystems, Inc. to assist in its use, correction, * modification or enhancement. * * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE * OR ANY PART THEREOF. * * In no event will Sun Microsystems, Inc. be liable for any lost revenue * or profits or other special, indirect and consequential damages, even if * Sun has been advised of the possibility of such damages. * * Sun Microsystems, Inc. * 2550 Garcia Avenue * Mountain View, California 94043 */ /* * g723_24.c * * Description: * * g723_24_encoder(), g723_24_decoder() * * These routines comprise an implementation of the CCITT G.723 24 Kbps * ADPCM coding algorithm. Essentially, this implementation is identical to * the bit level description except for a few deviations which take advantage * of workstation attributes, such as hardware 2's complement arithmetic. * */ #include "g72x.h" #include "g711.h" /* * Maps G.723_24 code word to reconstructed scale factor normalized log * magnitude values. */ static short _dqlntab[8] = {-2048, 135, 273, 373, 373, 273, 135, -2048}; /* Maps G.723_24 code word to log of scale factor multiplier. */ static short _witab[8] = {-128, 960, 4384, 18624, 18624, 4384, 960, -128}; /* * Maps G.723_24 code words to a set of values whose long and short * term averages are computed and then compared to give an indication * how stationary (steady state) the signal is. */ static short _fitab[8] = {0, 0x200, 0x400, 0xE00, 0xE00, 0x400, 0x200, 0}; static short qtab_723_24[3] = {8, 218, 331}; /* * g723_24_encoder() * * Encodes a linear PCM, A-law or u-law input sample and returns its 3-bit code. * Returns -1 if invalid input coding value. */ int g723_24_encoder( int sl, int in_coding, struct g72x_state *state_ptr) { short sei, sezi, se, sez; /* ACCUM */ short d; /* SUBTA */ short y; /* MIX */ short sr; /* ADDB */ short dqsez; /* ADDC */ short dq, i; switch (in_coding) { /* linearize input sample to 14-bit PCM */ case AUDIO_ENCODING_ALAW: sl = alaw2linear(sl) >> 2; break; case AUDIO_ENCODING_ULAW: sl = ulaw2linear(sl) >> 2; break; case AUDIO_ENCODING_LINEAR: sl >>= 2; /* sl of 14-bit dynamic range */ break; default: return (-1); } sezi = predictor_zero(state_ptr); sez = sezi >> 1; sei = sezi + predictor_pole(state_ptr); se = sei >> 1; /* se = estimated signal */ d = sl - se; /* d = estimation diff. */ /* quantize prediction difference d */ y = step_size(state_ptr); /* quantizer step size */ i = quantize(d, y, qtab_723_24, 3); /* i = ADPCM code */ dq = reconstruct(i & 4, _dqlntab[i], y); /* quantized diff. */ sr = (dq < 0) ? se - (dq & 0x3FFF) : se + dq; /* reconstructed signal */ dqsez = sr + sez - se; /* pole prediction diff. */ update(3, y, _witab[i], _fitab[i], dq, sr, dqsez, state_ptr); return (i); } /* * g723_24_decoder() * * Decodes a 3-bit CCITT G.723_24 ADPCM code and returns * the resulting 16-bit linear PCM, A-law or u-law sample value. * -1 is returned if the output coding is unknown. */ int g723_24_decoder( int i, int out_coding, struct g72x_state *state_ptr) { short sezi, sei, sez, se; /* ACCUM */ short y; /* MIX */ short sr; /* ADDB */ short dq; short dqsez; i &= 0x07; /* mask to get proper bits */ sezi = predictor_zero(state_ptr); sez = sezi >> 1; sei = sezi + predictor_pole(state_ptr); se = sei >> 1; /* se = estimated signal */ y = step_size(state_ptr); /* adaptive quantizer step size */ dq = reconstruct(i & 0x04, _dqlntab[i], y); /* unquantize pred diff */ sr = (dq < 0) ? (se - (dq & 0x3FFF)) : (se + dq); /* reconst. signal */ dqsez = sr - se + sez; /* pole prediction diff. */ update(3, y, _witab[i], _fitab[i], dq, sr, dqsez, state_ptr); switch (out_coding) { case AUDIO_ENCODING_ALAW: return (tandem_adjust_alaw(sr, se, y, i, 4, qtab_723_24)); case AUDIO_ENCODING_ULAW: return (tandem_adjust_ulaw(sr, se, y, i, 4, qtab_723_24)); case AUDIO_ENCODING_LINEAR: return (sr << 2); /* sr was of 14-bit dynamic range */ default: return (-1); } } twinkle-1.10.1/src/audio/g723_40.cpp000066400000000000000000000126461277565361200166570ustar00rootroot00000000000000/* * This source code is a product of Sun Microsystems, Inc. and is provided * for unrestricted use. Users may copy or modify this source code without * charge. * * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. * * Sun source code is provided with no support and without any obligation on * the part of Sun Microsystems, Inc. to assist in its use, correction, * modification or enhancement. * * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE * OR ANY PART THEREOF. * * In no event will Sun Microsystems, Inc. be liable for any lost revenue * or profits or other special, indirect and consequential damages, even if * Sun has been advised of the possibility of such damages. * * Sun Microsystems, Inc. * 2550 Garcia Avenue * Mountain View, California 94043 */ /* * g723_40.c * * Description: * * g723_40_encoder(), g723_40_decoder() * * These routines comprise an implementation of the CCITT G.723 40Kbps * ADPCM coding algorithm. Essentially, this implementation is identical to * the bit level description except for a few deviations which * take advantage of workstation attributes, such as hardware 2's * complement arithmetic. * * The deviation from the bit level specification (lookup tables), * preserves the bit level performance specifications. * * As outlined in the G.723 Recommendation, the algorithm is broken * down into modules. Each section of code below is preceded by * the name of the module which it is implementing. * */ #include "g72x.h" #include "g711.h" /* * Maps G.723_40 code word to ructeconstructed scale factor normalized log * magnitude values. */ static short _dqlntab[32] = {-2048, -66, 28, 104, 169, 224, 274, 318, 358, 395, 429, 459, 488, 514, 539, 566, 566, 539, 514, 488, 459, 429, 395, 358, 318, 274, 224, 169, 104, 28, -66, -2048}; /* Maps G.723_40 code word to log of scale factor multiplier. */ static short _witab[32] = {448, 448, 768, 1248, 1280, 1312, 1856, 3200, 4512, 5728, 7008, 8960, 11456, 14080, 16928, 22272, 22272, 16928, 14080, 11456, 8960, 7008, 5728, 4512, 3200, 1856, 1312, 1280, 1248, 768, 448, 448}; /* * Maps G.723_40 code words to a set of values whose long and short * term averages are computed and then compared to give an indication * how stationary (steady state) the signal is. */ static short _fitab[32] = {0, 0, 0, 0, 0, 0x200, 0x200, 0x200, 0x200, 0x200, 0x400, 0x600, 0x800, 0xA00, 0xC00, 0xC00, 0xC00, 0xC00, 0xA00, 0x800, 0x600, 0x400, 0x200, 0x200, 0x200, 0x200, 0x200, 0, 0, 0, 0, 0}; static short qtab_723_40[15] = {-122, -16, 68, 139, 198, 250, 298, 339, 378, 413, 445, 475, 502, 528, 553}; /* * g723_40_encoder() * * Encodes a 16-bit linear PCM, A-law or u-law input sample and retuens * the resulting 5-bit CCITT G.723 40Kbps code. * Returns -1 if the input coding value is invalid. */ int g723_40_encoder( int sl, int in_coding, struct g72x_state *state_ptr) { short sei, sezi, se, sez; /* ACCUM */ short d; /* SUBTA */ short y; /* MIX */ short sr; /* ADDB */ short dqsez; /* ADDC */ short dq, i; switch (in_coding) { /* linearize input sample to 14-bit PCM */ case AUDIO_ENCODING_ALAW: sl = alaw2linear(sl) >> 2; break; case AUDIO_ENCODING_ULAW: sl = ulaw2linear(sl) >> 2; break; case AUDIO_ENCODING_LINEAR: sl >>= 2; /* sl of 14-bit dynamic range */ break; default: return (-1); } sezi = predictor_zero(state_ptr); sez = sezi >> 1; sei = sezi + predictor_pole(state_ptr); se = sei >> 1; /* se = estimated signal */ d = sl - se; /* d = estimation difference */ /* quantize prediction difference */ y = step_size(state_ptr); /* adaptive quantizer step size */ i = quantize(d, y, qtab_723_40, 15); /* i = ADPCM code */ dq = reconstruct(i & 0x10, _dqlntab[i], y); /* quantized diff */ sr = (dq < 0) ? se - (dq & 0x7FFF) : se + dq; /* reconstructed signal */ dqsez = sr + sez - se; /* dqsez = pole prediction diff. */ update(5, y, _witab[i], _fitab[i], dq, sr, dqsez, state_ptr); return (i); } /* * g723_40_decoder() * * Decodes a 5-bit CCITT G.723 40Kbps code and returns * the resulting 16-bit linear PCM, A-law or u-law sample value. * -1 is returned if the output coding is unknown. */ int g723_40_decoder( int i, int out_coding, struct g72x_state *state_ptr) { short sezi, sei, sez, se; /* ACCUM */ short y; /* MIX */ short sr; /* ADDB */ short dq; short dqsez; i &= 0x1f; /* mask to get proper bits */ sezi = predictor_zero(state_ptr); sez = sezi >> 1; sei = sezi + predictor_pole(state_ptr); se = sei >> 1; /* se = estimated signal */ y = step_size(state_ptr); /* adaptive quantizer step size */ dq = reconstruct(i & 0x10, _dqlntab[i], y); /* estimation diff. */ sr = (dq < 0) ? (se - (dq & 0x7FFF)) : (se + dq); /* reconst. signal */ dqsez = sr - se + sez; /* pole prediction diff. */ update(5, y, _witab[i], _fitab[i], dq, sr, dqsez, state_ptr); switch (out_coding) { case AUDIO_ENCODING_ALAW: return (tandem_adjust_alaw(sr, se, y, i, 0x10, qtab_723_40)); case AUDIO_ENCODING_ULAW: return (tandem_adjust_ulaw(sr, se, y, i, 0x10, qtab_723_40)); case AUDIO_ENCODING_LINEAR: return (sr << 2); /* sr was of 14-bit dynamic range */ default: return (-1); } } twinkle-1.10.1/src/audio/g72x.cpp000066400000000000000000000340601277565361200164530ustar00rootroot00000000000000/* * This source code is a product of Sun Microsystems, Inc. and is provided * for unrestricted use. Users may copy or modify this source code without * charge. * * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. * * Sun source code is provided with no support and without any obligation on * the part of Sun Microsystems, Inc. to assist in its use, correction, * modification or enhancement. * * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE * OR ANY PART THEREOF. * * In no event will Sun Microsystems, Inc. be liable for any lost revenue * or profits or other special, indirect and consequential damages, even if * Sun has been advised of the possibility of such damages. * * Sun Microsystems, Inc. * 2550 Garcia Avenue * Mountain View, California 94043 */ /* * g72x.c * * Common routines for G.721 and G.723 conversions. */ #include #include "g72x.h" #include "g711.h" static short power2[15] = {1, 2, 4, 8, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000}; /* * quan() * * quantizes the input val against the table of size short integers. * It returns i if table[i - 1] <= val < table[i]. * * Using linear search for simple coding. */ static int quan( int val, short *table, int size) { int i; for (i = 0; i < size; i++) if (val < *table++) break; return (i); } /* * fmult() * * returns the integer product of the 14-bit integer "an" and * "floating point" representation (4-bit exponent, 6-bit mantessa) "srn". */ static int fmult( int an, int srn) { short anmag, anexp, anmant; short wanexp, wanmant; short retval; anmag = (an > 0) ? an : ((-an) & 0x1FFF); anexp = quan(anmag, power2, 15) - 6; anmant = (anmag == 0) ? 32 : (anexp >= 0) ? anmag >> anexp : anmag << -anexp; wanexp = anexp + ((srn >> 6) & 0xF) - 13; wanmant = (anmant * (srn & 077) + 0x30) >> 4; retval = (wanexp >= 0) ? ((wanmant << wanexp) & 0x7FFF) : (wanmant >> -wanexp); return (((an ^ srn) < 0) ? -retval : retval); } /* * g72x_init_state() * * This routine initializes and/or resets the g72x_state structure * pointed to by 'state_ptr'. * All the initial state values are specified in the CCITT G.721 document. */ void g72x_init_state( struct g72x_state *state_ptr) { int cnta; state_ptr->yl = 34816; state_ptr->yu = 544; state_ptr->dms = 0; state_ptr->dml = 0; state_ptr->ap = 0; for (cnta = 0; cnta < 2; cnta++) { state_ptr->a[cnta] = 0; state_ptr->pk[cnta] = 0; state_ptr->sr[cnta] = 32; } for (cnta = 0; cnta < 6; cnta++) { state_ptr->b[cnta] = 0; state_ptr->dq[cnta] = 32; } state_ptr->td = 0; } /* * predictor_zero() * * computes the estimated signal from 6-zero predictor. * */ int predictor_zero( struct g72x_state *state_ptr) { int i; int sezi; sezi = fmult(state_ptr->b[0] >> 2, state_ptr->dq[0]); for (i = 1; i < 6; i++) /* ACCUM */ sezi += fmult(state_ptr->b[i] >> 2, state_ptr->dq[i]); return (sezi); } /* * predictor_pole() * * computes the estimated signal from 2-pole predictor. * */ int predictor_pole( struct g72x_state *state_ptr) { return (fmult(state_ptr->a[1] >> 2, state_ptr->sr[1]) + fmult(state_ptr->a[0] >> 2, state_ptr->sr[0])); } /* * step_size() * * computes the quantization step size of the adaptive quantizer. * */ int step_size( struct g72x_state *state_ptr) { int y; int dif; int al; if (state_ptr->ap >= 256) return (state_ptr->yu); else { y = state_ptr->yl >> 6; dif = state_ptr->yu - y; al = state_ptr->ap >> 2; if (dif > 0) y += (dif * al) >> 6; else if (dif < 0) y += (dif * al + 0x3F) >> 6; return (y); } } /* * quantize() * * Given a raw sample, 'd', of the difference signal and a * quantization step size scale factor, 'y', this routine returns the * ADPCM codeword to which that sample gets quantized. The step * size scale factor division operation is done in the log base 2 domain * as a subtraction. */ int quantize( int d, /* Raw difference signal sample */ int y, /* Step size multiplier */ short *table, /* quantization table */ int size) /* table size of short integers */ { short dqm; /* Magnitude of 'd' */ short exp; /* Integer part of base 2 log of 'd' */ short mant; /* Fractional part of base 2 log */ short dl; /* Log of magnitude of 'd' */ short dln; /* Step size scale factor normalized log */ int i; /* * LOG * * Compute base 2 log of 'd', and store in 'dl'. */ dqm = abs(d); exp = quan(dqm >> 1, power2, 15); mant = ((dqm << 7) >> exp) & 0x7F; /* Fractional portion. */ dl = (exp << 7) + mant; /* * SUBTB * * "Divide" by step size multiplier. */ dln = dl - (y >> 2); /* * QUAN * * Obtain codword i for 'd'. */ i = quan(dln, table, size); if (d < 0) /* take 1's complement of i */ return ((size << 1) + 1 - i); else if (i == 0) /* take 1's complement of 0 */ return ((size << 1) + 1); /* new in 1988 */ else return (i); } /* * reconstruct() * * Returns reconstructed difference signal 'dq' obtained from * codeword 'i' and quantization step size scale factor 'y'. * Multiplication is performed in log base 2 domain as addition. */ int reconstruct( int sign, /* 0 for non-negative value */ int dqln, /* G.72x codeword */ int y) /* Step size multiplier */ { short dql; /* Log of 'dq' magnitude */ short dex; /* Integer part of log */ short dqt; short dq; /* Reconstructed difference signal sample */ dql = dqln + (y >> 2); /* ADDA */ if (dql < 0) { return ((sign) ? -0x8000 : 0); } else { /* ANTILOG */ dex = (dql >> 7) & 15; dqt = 128 + (dql & 127); dq = (dqt << 7) >> (14 - dex); return ((sign) ? (dq - 0x8000) : dq); } } /* * update() * * updates the state variables for each output code */ void update( int code_size, /* distinguish 723_40 with others */ int y, /* quantizer step size */ int wi, /* scale factor multiplier */ int fi, /* for long/short term energies */ int dq, /* quantized prediction difference */ int sr, /* reconstructed signal */ int dqsez, /* difference from 2-pole predictor */ struct g72x_state *state_ptr) /* coder state pointer */ { int cnt; short mag, exp; /* Adaptive predictor, FLOAT A */ short a2p = 0; /* LIMC */ short a1ul; /* UPA1 */ short pks1; /* UPA2 */ short fa1; char tr; /* tone/transition detector */ short ylint, thr2, dqthr; short ylfrac, thr1; short pk0; pk0 = (dqsez < 0) ? 1 : 0; /* needed in updating predictor poles */ mag = dq & 0x7FFF; /* prediction difference magnitude */ /* TRANS */ ylint = state_ptr->yl >> 15; /* exponent part of yl */ ylfrac = (state_ptr->yl >> 10) & 0x1F; /* fractional part of yl */ thr1 = (32 + ylfrac) << ylint; /* threshold */ thr2 = (ylint > 9) ? 31 << 10 : thr1; /* limit thr2 to 31 << 10 */ dqthr = (thr2 + (thr2 >> 1)) >> 1; /* dqthr = 0.75 * thr2 */ if (state_ptr->td == 0) /* signal supposed voice */ tr = 0; else if (mag <= dqthr) /* supposed data, but small mag */ tr = 0; /* treated as voice */ else /* signal is data (modem) */ tr = 1; /* * Quantizer scale factor adaptation. */ /* FUNCTW & FILTD & DELAY */ /* update non-steady state step size multiplier */ state_ptr->yu = y + ((wi - y) >> 5); /* LIMB */ if (state_ptr->yu < 544) /* 544 <= yu <= 5120 */ state_ptr->yu = 544; else if (state_ptr->yu > 5120) state_ptr->yu = 5120; /* FILTE & DELAY */ /* update steady state step size multiplier */ state_ptr->yl += state_ptr->yu + ((-state_ptr->yl) >> 6); /* * Adaptive predictor coefficients. */ if (tr == 1) { /* reset a's and b's for modem signal */ state_ptr->a[0] = 0; state_ptr->a[1] = 0; state_ptr->b[0] = 0; state_ptr->b[1] = 0; state_ptr->b[2] = 0; state_ptr->b[3] = 0; state_ptr->b[4] = 0; state_ptr->b[5] = 0; } else { /* update a's and b's */ pks1 = pk0 ^ state_ptr->pk[0]; /* UPA2 */ /* update predictor pole a[1] */ a2p = state_ptr->a[1] - (state_ptr->a[1] >> 7); if (dqsez != 0) { fa1 = (pks1) ? state_ptr->a[0] : -state_ptr->a[0]; if (fa1 < -8191) /* a2p = function of fa1 */ a2p -= 0x100; else if (fa1 > 8191) a2p += 0xFF; else a2p += fa1 >> 5; if (pk0 ^ state_ptr->pk[1]) /* LIMC */ if (a2p <= -12160) a2p = -12288; else if (a2p >= 12416) a2p = 12288; else a2p -= 0x80; else if (a2p <= -12416) a2p = -12288; else if (a2p >= 12160) a2p = 12288; else a2p += 0x80; } /* TRIGB & DELAY */ state_ptr->a[1] = a2p; /* UPA1 */ /* update predictor pole a[0] */ state_ptr->a[0] -= state_ptr->a[0] >> 8; if (dqsez != 0) if (pks1 == 0) state_ptr->a[0] += 192; else state_ptr->a[0] -= 192; /* LIMD */ a1ul = 15360 - a2p; if (state_ptr->a[0] < -a1ul) state_ptr->a[0] = -a1ul; else if (state_ptr->a[0] > a1ul) state_ptr->a[0] = a1ul; /* UPB : update predictor zeros b[6] */ for (cnt = 0; cnt < 6; cnt++) { if (code_size == 5) /* for 40Kbps G.723 */ state_ptr->b[cnt] -= state_ptr->b[cnt] >> 9; else /* for G.721 and 24Kbps G.723 */ state_ptr->b[cnt] -= state_ptr->b[cnt] >> 8; if (dq & 0x7FFF) { /* XOR */ if ((dq ^ state_ptr->dq[cnt]) >= 0) state_ptr->b[cnt] += 128; else state_ptr->b[cnt] -= 128; } } } for (cnt = 5; cnt > 0; cnt--) state_ptr->dq[cnt] = state_ptr->dq[cnt-1]; /* FLOAT A : convert dq[0] to 4-bit exp, 6-bit mantissa f.p. */ if (mag == 0) { state_ptr->dq[0] = (dq >= 0) ? 0x20 : 0xFC20; } else { exp = quan(mag, power2, 15); state_ptr->dq[0] = (dq >= 0) ? (exp << 6) + ((mag << 6) >> exp) : (exp << 6) + ((mag << 6) >> exp) - 0x400; } state_ptr->sr[1] = state_ptr->sr[0]; /* FLOAT B : convert sr to 4-bit exp., 6-bit mantissa f.p. */ if (sr == 0) { state_ptr->sr[0] = 0x20; } else if (sr > 0) { exp = quan(sr, power2, 15); state_ptr->sr[0] = (exp << 6) + ((sr << 6) >> exp); } else if (sr > -32768) { mag = -sr; exp = quan(mag, power2, 15); state_ptr->sr[0] = (exp << 6) + ((mag << 6) >> exp) - 0x400; } else state_ptr->sr[0] = 0xFC20; /* DELAY A */ state_ptr->pk[1] = state_ptr->pk[0]; state_ptr->pk[0] = pk0; /* TONE */ if (tr == 1) /* this sample has been treated as data */ state_ptr->td = 0; /* next one will be treated as voice */ else if (a2p < -11776) /* small sample-to-sample correlation */ state_ptr->td = 1; /* signal may be data */ else /* signal is voice */ state_ptr->td = 0; /* * Adaptation speed control. */ state_ptr->dms += (fi - state_ptr->dms) >> 5; /* FILTA */ state_ptr->dml += (((fi << 2) - state_ptr->dml) >> 7); /* FILTB */ if (tr == 1) state_ptr->ap = 256; else if (y < 1536) /* SUBTC */ state_ptr->ap += (0x200 - state_ptr->ap) >> 4; else if (state_ptr->td == 1) state_ptr->ap += (0x200 - state_ptr->ap) >> 4; else if (abs((state_ptr->dms << 2) - state_ptr->dml) >= (state_ptr->dml >> 3)) state_ptr->ap += (0x200 - state_ptr->ap) >> 4; else state_ptr->ap += (-state_ptr->ap) >> 4; } /* * tandem_adjust(sr, se, y, i, sign) * * At the end of ADPCM decoding, it simulates an encoder which may be receiving * the output of this decoder as a tandem process. If the output of the * simulated encoder differs from the input to this decoder, the decoder output * is adjusted by one level of A-law or u-law codes. * * Input: * sr decoder output linear PCM sample, * se predictor estimate sample, * y quantizer step size, * i decoder input code, * sign sign bit of code i * * Return: * adjusted A-law or u-law compressed sample. */ int tandem_adjust_alaw( int sr, /* decoder output linear PCM sample */ int se, /* predictor estimate sample */ int y, /* quantizer step size */ int i, /* decoder input code */ int sign, short *qtab) { unsigned char sp; /* A-law compressed 8-bit code */ short dx; /* prediction error */ char id; /* quantized prediction error */ int sd; /* adjusted A-law decoded sample value */ int im; /* biased magnitude of i */ int imx; /* biased magnitude of id */ if (sr <= -32768) sr = -1; sp = linear2alaw((sr >> 1) << 3); /* short to A-law compression */ dx = (alaw2linear(sp) >> 2) - se; /* 16-bit prediction error */ id = quantize(dx, y, qtab, sign - 1); if (id == i) { /* no adjustment on sp */ return (sp); } else { /* sp adjustment needed */ /* ADPCM codes : 8, 9, ... F, 0, 1, ... , 6, 7 */ im = i ^ sign; /* 2's complement to biased unsigned */ imx = id ^ sign; if (imx > im) { /* sp adjusted to next lower value */ if (sp & 0x80) { sd = (sp == 0xD5) ? 0x55 : ((sp ^ 0x55) - 1) ^ 0x55; } else { sd = (sp == 0x2A) ? 0x2A : ((sp ^ 0x55) + 1) ^ 0x55; } } else { /* sp adjusted to next higher value */ if (sp & 0x80) sd = (sp == 0xAA) ? 0xAA : ((sp ^ 0x55) + 1) ^ 0x55; else sd = (sp == 0x55) ? 0xD5 : ((sp ^ 0x55) - 1) ^ 0x55; } return (sd); } } int tandem_adjust_ulaw( int sr, /* decoder output linear PCM sample */ int se, /* predictor estimate sample */ int y, /* quantizer step size */ int i, /* decoder input code */ int sign, short *qtab) { unsigned char sp; /* u-law compressed 8-bit code */ short dx; /* prediction error */ char id; /* quantized prediction error */ int sd; /* adjusted u-law decoded sample value */ int im; /* biased magnitude of i */ int imx; /* biased magnitude of id */ if (sr <= -32768) sr = 0; sp = linear2ulaw(sr << 2); /* short to u-law compression */ dx = (ulaw2linear(sp) >> 2) - se; /* 16-bit prediction error */ id = quantize(dx, y, qtab, sign - 1); if (id == i) { return (sp); } else { /* ADPCM codes : 8, 9, ... F, 0, 1, ... , 6, 7 */ im = i ^ sign; /* 2's complement to biased unsigned */ imx = id ^ sign; if (imx > im) { /* sp adjusted to next lower value */ if (sp & 0x80) sd = (sp == 0xFF) ? 0x7E : sp + 1; else sd = (sp == 0) ? 0 : sp - 1; } else { /* sp adjusted to next higher value */ if (sp & 0x80) sd = (sp == 0x80) ? 0x80 : sp - 1; else sd = (sp == 0x7F) ? 0xFE : sp + 1; } return (sd); } } twinkle-1.10.1/src/audio/g72x.h000066400000000000000000000110741277565361200161200ustar00rootroot00000000000000/* * This source code is a product of Sun Microsystems, Inc. and is provided * for unrestricted use. Users may copy or modify this source code without * charge. * * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. * * Sun source code is provided with no support and without any obligation on * the part of Sun Microsystems, Inc. to assist in its use, correction, * modification or enhancement. * * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE * OR ANY PART THEREOF. * * In no event will Sun Microsystems, Inc. be liable for any lost revenue * or profits or other special, indirect and consequential damages, even if * Sun has been advised of the possibility of such damages. * * Sun Microsystems, Inc. * 2550 Garcia Avenue * Mountain View, California 94043 */ /* * g72x.h * * Header file for CCITT conversion routines. * */ #ifndef _G72X_H #define _G72X_H #define AUDIO_ENCODING_ULAW (1) /* ISDN u-law */ #define AUDIO_ENCODING_ALAW (2) /* ISDN A-law */ #define AUDIO_ENCODING_LINEAR (3) /* PCM 2's-complement (0-center) */ /* * The following is the definition of the state structure * used by the G.721/G.723 encoder and decoder to preserve their internal * state between successive calls. The meanings of the majority * of the state structure fields are explained in detail in the * CCITT Recommendation G.721. The field names are essentially indentical * to variable names in the bit level description of the coding algorithm * included in this Recommendation. */ struct g72x_state { long yl; /* Locked or steady state step size multiplier. */ short yu; /* Unlocked or non-steady state step size multiplier. */ short dms; /* Short term energy estimate. */ short dml; /* Long term energy estimate. */ short ap; /* Linear weighting coefficient of 'yl' and 'yu'. */ short a[2]; /* Coefficients of pole portion of prediction filter. */ short b[6]; /* Coefficients of zero portion of prediction filter. */ short pk[2]; /* * Signs of previous two samples of a partially * reconstructed signal. */ short dq[6]; /* * Previous 6 samples of the quantized difference * signal represented in an internal floating point * format. */ short sr[2]; /* * Previous 2 samples of the quantized difference * signal represented in an internal floating point * format. */ char td; /* delayed tone detect, new in 1988 version */ }; int predictor_zero(struct g72x_state *state_ptr); int predictor_pole(struct g72x_state *state_ptr); int step_size(struct g72x_state *state_ptr); int quantize( int d, /* Raw difference signal sample */ int y, /* Step size multiplier */ short *table, /* quantization table */ int size); /* table size of short integers */ int reconstruct( int sign, /* 0 for non-negative value */ int dqln, /* G.72x codeword */ int y); /* Step size multiplier */ void update( int code_size, /* distinguish 723_40 with others */ int y, /* quantizer step size */ int wi, /* scale factor multiplier */ int fi, /* for long/short term energies */ int dq, /* quantized prediction difference */ int sr, /* reconstructed signal */ int dqsez, /* difference from 2-pole predictor */ struct g72x_state *state_ptr); int tandem_adjust_alaw( int sr, /* decoder output linear PCM sample */ int se, /* predictor estimate sample */ int y, /* quantizer step size */ int i, /* decoder input code */ int sign, short *qtab); int tandem_adjust_ulaw( int sr, /* decoder output linear PCM sample */ int se, /* predictor estimate sample */ int y, /* quantizer step size */ int i, /* decoder input code */ int sign, short *qtab); void g72x_init_state(struct g72x_state *); int g721_encoder( int sample, int in_coding, struct g72x_state *state_ptr); int g721_decoder( int code, int out_coding, struct g72x_state *state_ptr); int g723_16_encoder( int sample, int in_coding, struct g72x_state *state_ptr); int g723_16_decoder( int code, int out_coding, struct g72x_state *state_ptr); int g723_24_encoder( int sample, int in_coding, struct g72x_state *state_ptr); int g723_24_decoder( int code, int out_coding, struct g72x_state *state_ptr); int g723_40_encoder( int sample, int in_coding, struct g72x_state *state_ptr); int g723_40_decoder( int code, int out_coding, struct g72x_state *state_ptr); #endif /* !_G72X_H */ twinkle-1.10.1/src/audio/media_buffer.cpp000066400000000000000000000057371277565361200203050ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "media_buffer.h" #include #include "audits/memman.h" //////////// // PUBLIC //////////// t_media_buffer::t_media_buffer(int size) { buf_size = size; empty = true; buffer = new unsigned char[size]; MEMMAN_NEW_ARRAY(buffer); pos_start = 0; pos_end = 0; } t_media_buffer::~t_media_buffer() { MEMMAN_DELETE_ARRAY(buffer); delete [] buffer; } void t_media_buffer::add(unsigned char *data, int len) { int data_start, data_end; mtx.lock(); // The amount of data should fit in the buffer. if (len > buf_size) { mtx.unlock(); return; } int current_size_content = size_content(); if (empty) { data_start = 0; data_end = len - 1; pos_start = 0; empty = false; } else { data_start = (pos_end + 1) % buf_size; data_end = (data_start + len - 1) % buf_size; } // Copy the data into the buffer if (data_end >= data_start) { memcpy(buffer + data_start, data, len); } else { // The data wraps around the end of the buffer memcpy(buffer + data_start, data, buf_size - data_start); memcpy(buffer, data + buf_size - data_start, data_end + 1); } // Check if the new data wrapped over the start of the old data. // If so, then advance the start of the old data behind the end of the new // data as new data has erased the oldest data. if (buf_size - current_size_content < len) { pos_start = (data_end + 1) % buf_size; } pos_end = data_end; mtx.unlock(); } bool t_media_buffer::get(unsigned char *data, int len) { mtx.lock(); if (len > size_content()) { mtx.unlock(); return false; } // Retrieve the data from the buffer if (pos_start + len <= buf_size) { memcpy(data, buffer + pos_start, len); } else { // The data to be retrieved wraps around the end of // the buffer. memcpy(data, buffer + pos_start, buf_size - pos_start); memcpy(data + buf_size - pos_start, buffer, len - buf_size + pos_start); } pos_start = (pos_start + len) % buf_size; // Check if buffer is empty if (pos_start == (pos_end + 1) % buf_size) { empty = true; } mtx.unlock(); return true; } int t_media_buffer::size_content(void) { int len; mtx.lock(); if (empty) { len = 0; } else if (pos_end >= pos_start) { len = pos_end - pos_start + 1; } else { len = pos_end + buf_size - pos_start + 1; } mtx.unlock(); return len; } twinkle-1.10.1/src/audio/media_buffer.h000066400000000000000000000041531277565361200177410ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _MEDIA_BUFFER_H #define _MEDIA_BUFFER_H #include "threads/mutex.h" // A buffer for buffering media streams. // Used for conference calls to buffer one stream that needs to be // mixed with the main stream. class t_media_buffer { private: // The buffer. It is used as a ring buffer unsigned char *buffer; // Size of the buffer int buf_size; // Begin and end position of the buffer content. // pos_end points to the last byte of content. int pos_start; int pos_end; // Inidicates if buffer is empty bool empty; // Mutex to protect operations on the buffer t_recursive_mutex mtx; // Prevent this constructor from being used. t_media_buffer() {}; public: // Create a media buffer of size size. t_media_buffer(int size); ~t_media_buffer(); // Add data to buffer. If there is more data than buffer // space left, then old content will be removed from the // buffer. // - data is the data to be added // - len is the number of bytes to be added void add(unsigned char *data, int len); // Get data from the buffer. If there is not enough data // in the buffer, then no data is retrieved at all. // False is returned if data cannot be retrieved. // - data must point to a buffer of at least size len // - len is the amount of bytes to be retrieved bool get(unsigned char *data, int len); // Return the number of bytes contained by the buffer. int size_content(void); }; #endif twinkle-1.10.1/src/audio/rtp_telephone_event.cpp000066400000000000000000000043741277565361200217420ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "cassert" #include "rtp_telephone_event.h" #include void t_rtp_telephone_event::set_event(unsigned char _event) { event = _event; } void t_rtp_telephone_event::set_volume(unsigned char _volume) { volume = _volume; } void t_rtp_telephone_event::set_reserved(bool _reserved) { reserved = _reserved; } void t_rtp_telephone_event::set_end(bool _end) { end = _end; } void t_rtp_telephone_event::set_duration(unsigned short _duration) { duration = htons(_duration); } unsigned char t_rtp_telephone_event::get_event(void) const { return event; } unsigned char t_rtp_telephone_event::get_volume(void) const { return volume; } bool t_rtp_telephone_event::get_reserved(void) const { return reserved; } bool t_rtp_telephone_event::get_end(void) const { return end; } unsigned short t_rtp_telephone_event::get_duration(void) const { return ntohs(duration); } t_dtmf_ev char2dtmf_ev(char sym) { if (sym >= '0' && sym <= '9') return (sym - '0' + TEL_EV_DTMF_0); if (sym >= 'A' && sym <= 'D') return (sym - 'A' + TEL_EV_DTMF_A); if (sym >= 'a' && sym <= 'd') return (sym- 'a' + TEL_EV_DTMF_A); if (sym == '*') return TEL_EV_DTMF_STAR; if (sym == '#') return TEL_EV_DTMF_POUND; assert(false); return TEL_EV_DTMF_INVALID; } char dtmf_ev2char(t_dtmf_ev ev) { if (ev <= TEL_EV_DTMF_9) { return ev + '0' - TEL_EV_DTMF_0; } if (ev >= TEL_EV_DTMF_A && ev <= TEL_EV_DTMF_D) { return ev + 'A' - TEL_EV_DTMF_A; } if (ev == TEL_EV_DTMF_STAR) return '*'; if (ev == TEL_EV_DTMF_POUND) return '#'; assert(false); return '?'; } twinkle-1.10.1/src/audio/rtp_telephone_event.h000066400000000000000000000045601277565361200214040ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // RFC 2833 // RTP payload format for DTMF telephone events #ifndef _RTP_TELEPHONE_EVENT_H #define _RTP_TELEPHONE_EVENT_H // RFC 2833 3.10 // DTMF events typedef unsigned char t_dtmf_ev; #define TEL_EV_DTMF_0 0 #define TEL_EV_DTMF_1 1 #define TEL_EV_DTMF_2 2 #define TEL_EV_DTMF_3 3 #define TEL_EV_DTMF_4 4 #define TEL_EV_DTMF_5 5 #define TEL_EV_DTMF_6 6 #define TEL_EV_DTMF_7 7 #define TEL_EV_DTMF_8 8 #define TEL_EV_DTMF_9 9 #define TEL_EV_DTMF_STAR 10 #define TEL_EV_DTMF_POUND 11 #define TEL_EV_DTMF_A 12 #define TEL_EV_DTMF_B 13 #define TEL_EV_DTMF_C 14 #define TEL_EV_DTMF_D 15 #define TEL_EV_DTMF_INVALID ((t_dtmf_ev) 0xff) static inline bool is_valid_dtmf_ev(t_dtmf_ev ev) { return (ev <= TEL_EV_DTMF_D); } static inline bool is_valid_dtmf_sym(char s) { if (s >= '0' && s <= '9') return true; if (s >= 'a' && s <= 'd') return true; if (s >= 'A' && s <= 'D') return true; if (s == '*' || s == '#') return true; return false; } // RFC 2833 3.5 // Payload format (in network order!!) struct t_rtp_telephone_event { private: unsigned char event : 8; unsigned char volume : 6; bool reserved : 1; bool end : 1; unsigned short duration : 16; public: // Values set/get are in host order void set_event(unsigned char _event); void set_volume(unsigned char _volume); void set_reserved(bool _reserved); void set_end(bool _end); void set_duration(unsigned short _duration); unsigned char get_event(void) const; unsigned char get_volume(void) const; bool get_reserved(void) const; bool get_end(void) const; unsigned short get_duration(void) const; }; unsigned char char2dtmf_ev(char sym); char dtmf_ev2char(unsigned char ev); #endif twinkle-1.10.1/src/audio/tone_gen.cpp000066400000000000000000000127061277565361200174650ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include #include #include #include #include #include #include "tone_gen.h" #include "log.h" #include "sys_settings.h" #include "user.h" #include "userintf.h" #include "util.h" #include "audits/memman.h" #include "audio_device.h" // Number of samples read at once from the wav file #define NUM_SAMPLES_PER_TURN 1024 // Duration of one turn in ms #define DURATION_TURN (NUM_SAMPLES_PER_TURN * 1000 / wav_info.samplerate) // Main function for play thread void *tone_gen_play(void *arg) { ui->add_prohibited_thread(); t_tone_gen *tg = (t_tone_gen *)arg; tg->play(); ui->remove_prohibited_thread(); return NULL; } t_tone_gen::t_tone_gen(const string &filename, const t_audio_device &_dev_tone) : dev_tone(_dev_tone), sema_finished(0) { string f; wav_file = NULL; aio = 0; valid = false; data_buf = NULL; thr_play = NULL; loop = false; pause = 0; if (filename.size() == 0) return; // Add share directory to filename if (filename[0] != '/') { f = sys_config->get_dir_share(); f += "/"; f += filename; } else { f = filename; } wav_filename = f; memset(&wav_info, 0, sizeof(SF_INFO)); wav_file = sf_open(f.c_str(), SFM_READ, &wav_info); if (!wav_file) { string msg("Cannot open "); msg += f; log_file->write_report(msg, "t_tone_gen::t_tone_gen", LOG_NORMAL, LOG_WARNING); ui->cb_display_msg(msg, MSG_WARNING); return; } log_file->write_header("t_tone_gen::t_tone_gen"); log_file->write_raw("Opened "); log_file->write_raw(f); log_file->write_endl(); log_file->write_footer(); valid = true; stop_playing = false; } t_tone_gen::~t_tone_gen() { if (wav_file) { sf_close(wav_file); } if (aio) { MEMMAN_DELETE(aio); delete aio; } aio = 0; if (data_buf) { MEMMAN_DELETE_ARRAY(data_buf); delete [] data_buf; } if (thr_play) { MEMMAN_DELETE(thr_play); delete thr_play; } log_file->write_report("Deleted tone generator.", "t_tone_gen::~t_tone_gen"); } bool t_tone_gen::is_valid(void) const { return valid; } void t_tone_gen::play(void) { if (!valid) { log_file->write_report( "Tone generator is invalid. Cannot play tone", "t_tone_gen::play", LOG_NORMAL, LOG_WARNING); sema_finished.up(); return; } aio = t_audio_io::open(dev_tone, true, false, true, wav_info.channels, SAMPLEFORMAT_S16, wav_info.samplerate, false); if (!aio) { string msg("Failed to open sound card: "); msg += get_error_str(errno); log_file->write_report(msg, "t_tone_gen::play", LOG_NORMAL, LOG_WARNING); ui->cb_display_msg(msg, MSG_WARNING); sema_finished.up(); return; } log_file->write_report("Start playing tone.", "t_tone_gen::play"); do { // Each samples consists of #channels shorts data_buf = new short[NUM_SAMPLES_PER_TURN * wav_info.channels]; MEMMAN_NEW_ARRAY(data_buf); sf_count_t frames_read = NUM_SAMPLES_PER_TURN; while (frames_read == NUM_SAMPLES_PER_TURN) { if (stop_playing) break; // Play sample frames_read = sf_readf_short(wav_file, data_buf, NUM_SAMPLES_PER_TURN); if (frames_read > 0) { aio->write((unsigned char*)data_buf, frames_read * wav_info.channels * 2); } } MEMMAN_DELETE_ARRAY(data_buf); delete [] data_buf; data_buf = NULL; if (stop_playing) break; // Pause between repetitions if (loop) { // Play silence if (pause > 0) { data_buf = new short[NUM_SAMPLES_PER_TURN * wav_info.channels]; MEMMAN_NEW_ARRAY(data_buf); memset(data_buf, 0, NUM_SAMPLES_PER_TURN * wav_info.channels * 2); for (int i = 0; i < pause; i += DURATION_TURN) { aio->write((unsigned char*)data_buf, NUM_SAMPLES_PER_TURN * wav_info.channels * 2); if (stop_playing) break; } MEMMAN_DELETE_ARRAY(data_buf); delete [] data_buf; data_buf = NULL; } if (stop_playing) break; // Set file pointer back to start of data sf_seek(wav_file, 0, SEEK_SET); } } while (loop); log_file->write_report("Tone ended.", "t_tone_gen::play_tone"); sema_finished.up(); } void t_tone_gen::start_play_thread(bool _loop, int _pause) { loop = _loop; pause = _pause; thr_play = new t_thread(tone_gen_play, this); MEMMAN_NEW(thr_play); thr_play->detach(); } void t_tone_gen::stop(void) { log_file->write_report("Stopping tone.", "t_tone_gen::stop"); if (stop_playing) { log_file->write_report("Tone has stopped already.", "t_tone_gen::stop"); return; } // This will stop the playing thread. stop_playing = true; // The semaphore will be upped by the playing thread as soon // as playing finishes. sema_finished.down(); log_file->write_report("Tone stopped.", "t_tone_gen::stop"); if (aio) { MEMMAN_DELETE(aio); delete aio; aio = 0; } } twinkle-1.10.1/src/audio/tone_gen.h000066400000000000000000000036461277565361200171350ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _TONE_GEN_H #define _TONE_GEN_H #include #include #include #include "sys_settings.h" #include "threads/mutex.h" #include "threads/thread.h" #include "threads/sema.h" #ifndef _AUDIO_DEVICE_H class t_audio_io; #endif using namespace std; class t_tone_gen { private: string wav_filename; // name of wav file SNDFILE *wav_file; // SNDFILE pointer to wav file SF_INFO wav_info; // Information about format of the wav file t_audio_device dev_tone; // device to play tone t_audio_io* aio; // soundcard bool valid; // wav file is in a valid format bool stop_playing; // indicates if playing should stop t_thread *thr_play; // playing thread bool loop; // repeat playing int pause; // pause (ms) between repetitions short *data_buf; // buffer for reading sound samples t_semaphore sema_finished; // indicates if playing finished public: t_tone_gen(const string &filename, const t_audio_device &_dev_tone); ~t_tone_gen(); bool is_valid(void) const; // Play the wav file // loop = true -> repeat playing // pause is pause in ms between repetitions void start_play_thread(bool _loop, int _pause); void play(void); // Stop playing void stop(void); }; #endif twinkle-1.10.1/src/audio/twinkle_rtp_session.cpp000066400000000000000000000055771277565361200220040ustar00rootroot00000000000000 /* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include "twinkle_rtp_session.h" #include "log.h" #include "sys_settings.h" #define TWINKLE_ZID_FILE ".twinkle.zid" t_twinkle_rtp_session::~t_twinkle_rtp_session() {} #ifdef HAVE_ZRTP void t_twinkle_rtp_session::init_zrtp(void) { string zid_filename = sys_config->get_dir_user(); zid_filename += '/'; zid_filename += TWINKLE_ZID_FILE; if (initialize(zid_filename.c_str()) >=0) { zrtp_initialized = true; return; } // ZID file initialization failed. Maybe the ZID file // is corrupt. Try to remove it if (unlink(zid_filename.c_str()) < 0) { string msg = "Failed to remove "; msg += zid_filename; log_file->write_report(msg, "t_twinkle_rtp_session::init_zrtp", LOG_NORMAL, LOG_CRITICAL); return; } // Try to initialize once more if (initialize(zid_filename.c_str()) >= 0) { zrtp_initialized = true; } else { string msg = "Failed to initialize ZRTP - "; msg += zid_filename; log_file->write_report(msg, "t_twinkle_rtp_session::init_zrtp", LOG_NORMAL, LOG_CRITICAL); } } bool t_twinkle_rtp_session::is_zrtp_initialized(void) const { return zrtp_initialized; } t_twinkle_rtp_session::t_twinkle_rtp_session(const InetHostAddress &host) : SymmetricZRTPSession(host), zrtp_initialized(false) { init_zrtp(); } t_twinkle_rtp_session::t_twinkle_rtp_session(const InetHostAddress &host, unsigned short port) : SymmetricZRTPSession(host, port) , zrtp_initialized(false) { init_zrtp(); } #else t_twinkle_rtp_session::t_twinkle_rtp_session(const InetHostAddress &host) : SymmetricRTPSession(host) { } t_twinkle_rtp_session::t_twinkle_rtp_session(const InetHostAddress &host, unsigned short port) : SymmetricRTPSession(host, port) { } #endif uint32 t_twinkle_rtp_session::getLastTimestamp(const SyncSource *src) const { if ( src && !isMine(*src) ) return 0L; recvLock.readLock(); uint32 ts = 0; if (src != NULL) { SyncSourceLink* srcm = getLink(*src); IncomingRTPPktLink* l = srcm->getFirst(); while (l) { ts = l->getTimestamp(); l = l->getSrcNext(); } } else { IncomingRTPPktLink* l = recvFirst; while (l) { ts = l->getTimestamp(); l = l->getNext(); } } recvLock.unlock(); return ts; } twinkle-1.10.1/src/audio/twinkle_rtp_session.h000066400000000000000000000026731277565361200214430ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef TWINKLE_RTP_SESSION_H #define TWINKLE_RTP_SESSION_H #include "twinkle_config.h" #include #ifdef HAVE_ZRTP #include #else #include #endif using namespace std; using namespace ost; #ifdef HAVE_ZRTP class t_twinkle_rtp_session : public SymmetricZRTPSession { private: bool zrtp_initialized; void init_zrtp(void); public: bool is_zrtp_initialized(void) const; #else class t_twinkle_rtp_session : public SymmetricRTPSession { #endif public: virtual ~t_twinkle_rtp_session(); t_twinkle_rtp_session(const InetHostAddress &host); t_twinkle_rtp_session(const InetHostAddress &host, unsigned short port); uint32 getLastTimestamp(const SyncSource *src=NULL) const; }; #endif twinkle-1.10.1/src/audio/twinkle_zrtp_ui.cpp000066400000000000000000000255171277565361200211240ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Author: Werner Dittmann , (C) 2006 // Michel de Boer #include "twinkle_zrtp_ui.h" #ifdef HAVE_ZRTP #include "phone.h" #include "line.h" #include "log.h" #include "util.h" using namespace std; using namespace GnuZrtpCodes; extern t_phone *phone; // Initialize static data mapTwinkleZrtpUI::infoMap; mapTwinkleZrtpUI::warningMap; mapTwinkleZrtpUI::severeMap; mapTwinkleZrtpUI::zrtpMap; bool TwinkleZrtpUI::mapsDone = false; std::string TwinkleZrtpUI::unknownCode = "Unknown error code"; TwinkleZrtpUI::TwinkleZrtpUI(t_audio_session* session) : audioSession(session) { if (mapsDone) { return; } // Initialize error mapping infoMap.insert(pair(InfoHelloReceived, string("Hello received, preparing a Commit"))); infoMap.insert(pair(InfoCommitDHGenerated, string("Commit: Generated a public DH key"))); infoMap.insert(pair(InfoRespCommitReceived, string("Responder: Commit received, preparing DHPart1"))); infoMap.insert(pair(InfoDH1DHGenerated, string("DH1Part: Generated a public DH key"))); infoMap.insert(pair(InfoInitDH1Received, string("Initiator: DHPart1 received, preparing DHPart2"))); infoMap.insert(pair(InfoRespDH2Received, string("Responder: DHPart2 received, preparing Confirm1"))); infoMap.insert(pair(InfoInitConf1Received, string("Initiator: Confirm1 received, preparing Confirm2"))); infoMap.insert(pair(InfoRespConf2Received, string("Responder: Confirm2 received, preparing Conf2Ack"))); infoMap.insert(pair(InfoRSMatchFound, string("At least one retained secrets matches - security OK"))); infoMap.insert(pair(InfoSecureStateOn, string("Entered secure state"))); infoMap.insert(pair(InfoSecureStateOff, string("No more security for this session"))); warningMap.insert(pair(WarningDHAESmismatch, string("Commit contains an AES256 cipher but does not offer a Diffie-Helman 4096"))); warningMap.insert(pair(WarningGoClearReceived, string("Received a GoClear message"))); warningMap.insert(pair(WarningDHShort, string("Hello offers an AES256 cipher but does not offer a Diffie-Helman 4096"))); warningMap.insert(pair(WarningNoRSMatch, string("No retained shared secrets available - must verify SAS"))); warningMap.insert(pair(WarningCRCmismatch, string("Internal ZRTP packet checksum mismatch - packet dropped"))); warningMap.insert(pair(WarningSRTPauthError, string("Dropping packet because SRTP authentication failed!"))); warningMap.insert(pair(WarningSRTPreplayError, string("Dropping packet because SRTP replay check failed!"))); warningMap.insert(pair(WarningNoExpectedRSMatch, string("Valid retained shared secrets availabe but no matches found - must verify SAS"))); severeMap.insert(pair(SevereHelloHMACFailed, string("Hash HMAC check of Hello failed!"))); severeMap.insert(pair(SevereCommitHMACFailed, string("Hash HMAC check of Commit failed!"))); severeMap.insert(pair(SevereDH1HMACFailed, string("Hash HMAC check of DHPart1 failed!"))); severeMap.insert(pair(SevereDH2HMACFailed, string("Hash HMAC check of DHPart2 failed!"))); severeMap.insert(pair(SevereCannotSend, string("Cannot send data - connection or peer down?"))); severeMap.insert(pair(SevereProtocolError, string("Internal protocol error occured!"))); severeMap.insert(pair(SevereNoTimer, string("Cannot start a timer - internal resources exhausted?"))); severeMap.insert(pair(SevereTooMuchRetries, string("Too much retries during ZRTP negotiation - connection or peer down?"))); zrtpMap.insert(pair(MalformedPacket, string("Malformed packet (CRC OK, but wrong structure)"))); zrtpMap.insert(pair(CriticalSWError, string("Critical software error"))); zrtpMap.insert(pair(UnsuppZRTPVersion, string("Unsupported ZRTP version"))); zrtpMap.insert(pair(HelloCompMismatch, string("Hello components mismatch"))); zrtpMap.insert(pair(UnsuppHashType, string("Hash type not supported"))); zrtpMap.insert(pair(UnsuppCiphertype, string("Cipher type not supported"))); zrtpMap.insert(pair(UnsuppPKExchange, string("Public key exchange not supported"))); zrtpMap.insert(pair(UnsuppSRTPAuthTag, string("SRTP auth. tag not supported"))); zrtpMap.insert(pair(UnsuppSASScheme, string("SAS scheme not supported"))); zrtpMap.insert(pair(NoSharedSecret, string("No shared secret available, DH mode required"))); zrtpMap.insert(pair(DHErrorWrongPV, string("DH Error: bad pvi or pvr ( == 1, 0, or p-1)"))); zrtpMap.insert(pair(DHErrorWrongHVI, string("DH Error: hvi != hashed data"))); zrtpMap.insert(pair(SASuntrustedMiTM, string("Received relayed SAS from untrusted MiTM"))); zrtpMap.insert(pair(ConfirmHMACWrong, string("Auth. Error: Bad Confirm pkt HMAC"))); zrtpMap.insert(pair(NonceReused, string("Nonce reuse"))); zrtpMap.insert(pair(EqualZIDHello, string("Equal ZIDs in Hello"))); zrtpMap.insert(pair(GoCleatNotAllowed, string("GoClear packet received, but not allowed"))); mapsDone = true; } void TwinkleZrtpUI::secureOn(std::string cipher) { audioSession->set_is_encrypted(true); audioSession->set_srtp_cipher_mode(cipher); t_line *line = audioSession->get_line(); int lineno = line->get_line_number(); log_file->write_header("TwinkleZrtpUI::secureOn"); log_file->write_raw("Line "); log_file->write_raw(lineno + 1); log_file->write_raw(": audio encryption enabled: "); log_file->write_raw(cipher); log_file->write_endl(); log_file->write_footer(); ui->cb_async_line_encrypted(lineno, true); ui->cb_async_line_state_changed(); } void TwinkleZrtpUI::secureOff() { audioSession->set_is_encrypted(false); t_line *line = audioSession->get_line(); int lineno = line->get_line_number(); log_file->write_header("TwinkleZrtpUI::secureOff"); log_file->write_raw("Line "); log_file->write_raw(lineno + 1); log_file->write_raw(": audio encryption disabled.\n"); log_file->write_footer(); ui->cb_async_line_encrypted(lineno, false); ui->cb_async_line_state_changed(); } void TwinkleZrtpUI::showSAS(std::string sas, bool verified) { audioSession->set_zrtp_sas(sas); audioSession->set_zrtp_sas_confirmed(verified); t_line *line = audioSession->get_line(); int lineno = line->get_line_number(); log_file->write_header("TwinkleZrtpUI::showSAS"); log_file->write_raw("Line "); log_file->write_raw(lineno + 1); log_file->write_raw(": SAS ="); log_file->write_raw(sas); log_file->write_endl(); log_file->write_footer(); if (!verified) { ui->cb_async_show_zrtp_sas(lineno, sas); } ui->cb_async_line_state_changed(); } void TwinkleZrtpUI::confirmGoClear() { t_line *line = audioSession->get_line(); int lineno = line->get_line_number(); ui->cb_async_zrtp_confirm_go_clear(lineno); } void TwinkleZrtpUI::showMessage(MessageSeverity sev, int subCode) { t_line *line = audioSession->get_line(); int lineno = line->get_line_number(); string msg = "Line "; msg += int2str(lineno + 1); msg += ": "; msg += *mapCodesToString(sev, subCode); switch (sev) { case Info: log_file->write_report(msg, "TwinkleZrtpUI::showMessage", LOG_NORMAL, LOG_INFO); break; case Warning: log_file->write_report(msg, "TwinkleZrtpUI::showMessage", LOG_NORMAL, LOG_WARNING); break; default: log_file->write_report(msg, "TwinkleZrtpUI::showMessage", LOG_NORMAL, LOG_CRITICAL); } } void TwinkleZrtpUI::zrtpNegotiationFailed(MessageSeverity severity, int subCode) { t_line *line = audioSession->get_line(); int lineno = line->get_line_number(); string m = "Line "; m += int2str(lineno + 1); m += ": ZRTP negotiation failed.\n"; m += *mapCodesToString(severity, subCode); switch (severity) { case Info: log_file->write_report(m, "TwinkleZrtpUI::zrtpNegotiationFailed", LOG_NORMAL, LOG_INFO); break; case Warning: log_file->write_report(m, "TwinkleZrtpUI::zrtpNegotiationFailed", LOG_NORMAL, LOG_WARNING); break; default: log_file->write_report(m, "TwinkleZrtpUI::zrtpNegotiationFailed", LOG_NORMAL, LOG_CRITICAL); } } void TwinkleZrtpUI::zrtpNotSuppOther() { t_line *line = audioSession->get_line(); int lineno = line->get_line_number(); string msg = "Line "; msg += int2str(lineno + 1); msg += ": remote party does not support ZRTP."; log_file->write_report(msg, "TwinkleZrtpUI::zrtpNotSuppOther"); } const string *const TwinkleZrtpUI::mapCodesToString(MessageSeverity severity, int subCode) { string *m = &unknownCode; switch (severity) { case Info: m = &infoMap[subCode]; break; case Warning: m = &warningMap[subCode]; break; case Severe: m = &severeMap[subCode]; break; case ZrtpError: if (subCode < 0) { subCode *= -1; } m = &zrtpMap[subCode]; break; default: break; } return m; } #endif twinkle-1.10.1/src/audio/twinkle_zrtp_ui.h000066400000000000000000000055541277565361200205700ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Author: Werner Dittmann , (C) 2006 // Michel de Boer /** * @file * User interface call back functions for libzrtpcpp. */ #ifndef __TWINKLEZRTPUI_H_ #define __TWINKLEZRTPUI_H_ #include "twinkle_config.h" #ifdef HAVE_ZRTP #include #include #include #include "audio_session.h" #include "userintf.h" using namespace GnuZrtpCodes; /** User interface for libzrtpcpp. */ class TwinkleZrtpUI : public ZrtpUserCallback { public: /** * Constructor. * @param session [in] The audio session that is encrypted by ZRTP. */ TwinkleZrtpUI(t_audio_session* session); virtual ~TwinkleZrtpUI() {}; //@{ /** @name ZRTP call back functions called from the ZRTP thread */ virtual void secureOn(std::string cipher); virtual void secureOff(); virtual void showSAS(std::string sas, bool verified); virtual void confirmGoClear(); virtual void showMessage(MessageSeverity sev, int subCode); virtual void zrtpNegotiationFailed(MessageSeverity severity, int subCode); virtual void zrtpNotSuppOther(); //} private: /** Audio session associated with this user interface. */ t_audio_session* audioSession; //@{ /** @name Message mappings for libzrtpcpp */ static map infoMap; /**< Info messages */ static map warningMap; /**< Warnings */ static map severeMap; /**< Severe errors */ static map zrtpMap; /**< ZRTP errors */ static bool mapsDone; /**< Flag to indicate that maps are initialized */ static std::string unknownCode; /**< Unknown error code */ //@} /** * Map a message code returned by libzrtpcpp to a message text. * @param severity [in] The severity of the message. * @param subCode [in] The message code. * @return The message text. */ const string *const mapCodesToString(MessageSeverity severity, int subCode); }; #endif // HAVE_ZRTP #endif // __TWINKLEZRTPUI_H_ twinkle-1.10.1/src/audits/000077500000000000000000000000001277565361200153455ustar00rootroot00000000000000twinkle-1.10.1/src/audits/CMakeLists.txt000066400000000000000000000002061277565361200201030ustar00rootroot00000000000000project(libtwinkle-audits) set(LIBTWINKLE_AUDITS-SRCS memman.cpp ) add_library(libtwinkle-audits OBJECT ${LIBTWINKLE_AUDITS-SRCS}) twinkle-1.10.1/src/audits/memman.cpp000066400000000000000000000146471277565361200173370ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "memman.h" #include "log.h" #include "util.h" ////////////////////// // class t_ptr_info ////////////////////// t_ptr_info::t_ptr_info(const string &_filename, int _lineno, bool _is_array) : filename(_filename) { lineno = _lineno; is_array = _is_array; } ////////////////////// // class t_memman ////////////////////// t_memman::t_memman() { num_new = 0; num_new_duplicate = 0; num_delete = 0; num_delete_mismatch = 0; num_array_mixing = 0; } void t_memman::trc_new(void *p, const string &filename, int lineno, bool is_array) { mtx_memman.lock(); num_new++; // Check if pointer already exists map::iterator i; i = pointer_map.find(p); if (i != pointer_map.end()) { // Most likely this is an error in the usage of the // MEMMAN_NEW. A wrong pointer has been passed. num_new_duplicate++; // Unlock now. If memman gets called again via the log, // there will be no dead lock. mtx_memman.unlock(); log_file->write_header("t_memman::trc_new", LOG_MEMORY, LOG_WARNING); log_file->write_raw(filename); log_file->write_raw(", line "); log_file->write_raw(lineno); log_file->write_raw(": pointer to "); log_file->write_raw(ptr2str(p)); log_file->write_raw(" has already been allocated.\n"); log_file->write_raw("It was allocated here: "); log_file->write_raw(i->second.filename); log_file->write_raw(", line "); log_file->write_raw(i->second.lineno); log_file->write_endl(); log_file->write_footer(); return; } t_ptr_info pinfo(filename, lineno, is_array); pointer_map[p] = pinfo; mtx_memman.unlock(); } void t_memman::trc_delete(void *p, const string &filename, int lineno, bool is_array) { mtx_memman.lock(); num_delete++; map::iterator i; i = pointer_map.find(p); // Check if the pointer allocation has been reported if (i == pointer_map.end()) { num_delete_mismatch++; mtx_memman.unlock(); log_file->write_header("t_memman::trc_delete", LOG_MEMORY, LOG_WARNING); log_file->write_raw(filename); log_file->write_raw(", line "); log_file->write_raw(lineno); log_file->write_raw(": pointer to "); log_file->write_raw(ptr2str(p)); log_file->write_raw(" is deleted.\n"); log_file->write_raw("This pointer is not allocated however.\n"); log_file->write_footer(); return; } bool array_mismatch = (is_array != i->second.is_array); // Check mixing of array new/delete // NOTE: after the pointer has been erased from pointer_map, the // iterator i is invalid. // The mutex mtx_memman should be unlocked before logging to // avoid dead locks. if (array_mismatch) { num_array_mixing++; string allocation_filename = i->second.filename; int allocation_lineno = i->second.lineno; bool allocation_is_array = i->second.is_array; pointer_map.erase(p); mtx_memman.unlock(); log_file->write_header("t_memman::trc_delete", LOG_MEMORY, LOG_WARNING); log_file->write_raw(filename); log_file->write_raw(", line "); log_file->write_raw(lineno); log_file->write_raw(": pointer to "); log_file->write_raw(ptr2str(p)); log_file->write_raw(" is deleted "); if (is_array) { log_file->write_raw("as array (delete []).\n"); } else { log_file->write_raw("normally (delete).\n"); } log_file->write_raw("But it was allocated "); if (allocation_is_array) { log_file->write_raw("as array (new []) \n"); } else { log_file->write_raw("normally (new) \n"); } log_file->write_raw(allocation_filename); log_file->write_raw(", line "); log_file->write_raw(allocation_lineno); log_file->write_endl(); log_file->write_footer(); } else { pointer_map.erase(p); mtx_memman.unlock(); } } void t_memman::report_leaks(void) { mtx_memman.lock(); if (pointer_map.empty()) { if (num_array_mixing == 0) { log_file->write_report( "All pointers have correctly been deallocated.", "t_memman::report_leaks", LOG_MEMORY, LOG_INFO); } else { log_file->write_header("t_memman::report_leaks", LOG_MEMORY, LOG_WARNING); log_file->write_raw("All pointers have been deallocated."), log_file->write_raw( "Mixing of array/non-array caused memory loss though."); log_file->write_footer(); } mtx_memman.unlock(); return; } log_file->write_header("t_memman::report_leaks", LOG_MEMORY, LOG_WARNING); log_file->write_raw("The following pointers were never deallocated:\n"); for (map::const_iterator i = pointer_map.begin(); i != pointer_map.end(); i++) { log_file->write_raw(ptr2str(i->first)); log_file->write_raw(" allocated from "); log_file->write_raw(i->second.filename); log_file->write_raw(", line "); log_file->write_raw(i->second.lineno); log_file->write_endl(); } log_file->write_footer(); mtx_memman.unlock(); } void t_memman::report_stats(void) { mtx_memman.lock(); log_file->write_header("t_memman::report_stats", LOG_MEMORY, LOG_INFO); log_file->write_raw("Number of allocations: "); log_file->write_raw(num_new); log_file->write_endl(); log_file->write_raw("Number of duplicate allocations: "); log_file->write_raw(num_new_duplicate); log_file->write_endl(); log_file->write_raw("Number of de-allocations: "); log_file->write_raw(num_delete); log_file->write_endl(); log_file->write_raw("Number of mismatched de-allocations: "); log_file->write_raw(num_delete_mismatch); log_file->write_endl(); log_file->write_raw("Number of array/non-array mixed operations: "); log_file->write_raw(num_array_mixing); log_file->write_endl(); unsigned long num_unalloc = num_new - num_new_duplicate - num_delete + num_delete_mismatch; log_file->write_raw("Number of unallocated pointers: "); log_file->write_raw(num_unalloc); log_file->write_endl(); log_file->write_footer(); mtx_memman.unlock(); } twinkle-1.10.1/src/audits/memman.h000066400000000000000000000047471277565361200170040ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _MEMMAN_H #define _MEMMAN_H #include #include #include "threads/mutex.h" #define MEMMAN_NEW(ptr) memman->trc_new((ptr), __FILE__, __LINE__) #define MEMMAN_NEW_ARRAY(ptr) memman->trc_new((ptr), __FILE__, __LINE__, true) #define MEMMAN_DELETE(ptr) memman->trc_delete((ptr), __FILE__, __LINE__) #define MEMMAN_DELETE_ARRAY(ptr) memman->trc_delete((ptr), __FILE__, __LINE__, true) #define MEMMAN_REPORT { memman->report_stats(); memman->report_leaks(); } using namespace std; // Memory manager // Trace memory allocations and deallocations class t_ptr_info { public: // Src file from which pointer has been allocated string filename; // Line number of memman trace command tracing this pointer int lineno; // Indicates if the pointer points to an array bool is_array; t_ptr_info() {}; t_ptr_info(const string &_filename, int _lineno, bool _is_array); }; class t_memman { private: // Map of allocated pointers map pointer_map; // Statistics unsigned long num_new; // number of new's unsigned long num_new_duplicate; // number of duplicate new's unsigned long num_delete; // number of delete's unsigned long num_delete_mismatch; // number of delete's for without a new unsigned long num_array_mixing; // number of array/non-array mixes // Mutex to protect operations on the memory manager t_mutex mtx_memman; public: t_memman(); // Report pointer allocation void trc_new(void *p, const string &filename, int lineno, bool is_array = false); // Report pointer deallocation void trc_delete(void *p, const string &filename, int lineno, bool is_array = false); // Write a memory leak report to log void report_leaks(void); // Write statistics to log void report_stats(void); }; extern t_memman *memman; #endif twinkle-1.10.1/src/auth.cpp000066400000000000000000000134771277565361200155350ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include "auth.h" #include "log.h" #include "protocol.h" #include "user.h" #include "userintf.h" #include "util.h" extern string user_host; t_cr_cache_entry:: t_cr_cache_entry(const t_url &_to, const t_credentials &_cr, const string &_passwd, bool _proxy) : to(_to) { credentials = _cr; passwd = _passwd; proxy = _proxy; } list::iterator t_auth::find_cache_entry( const t_url &_to, const string &realm, bool proxy) { for (list::iterator i = cache.begin(); i != cache.end(); i++) { // RFC 3261 22.1 // Only the realm determines the protection space. // So the to-uri must not need to be compared as for HTTP // i.e. check i->to == _to must not be done. // As realm strings are globally unique there is no need // to check if the credentials are for a 407 or 401 response. // i.e. check i->proxy == proxy is not needed. if (i->credentials.digest_response.realm == realm) { return i; } } return cache.end(); } void t_auth::update_cache(const t_url &to, const t_credentials &cr, const string &passwd, bool proxy) { list::iterator i, j; i = find_cache_entry(to, cr.digest_response.realm, proxy); if (i == cache.end()) { if (cache.size() > AUTH_CACHE_SIZE) { cache.erase(cache.begin()); } cache.push_back(t_cr_cache_entry(to, cr, passwd, proxy)); } else { i->credentials = cr; i->passwd = passwd; // Move cache entry to end of the cache. // TODO: this can be more efficient by checking if the // entry is already at the end. t_cr_cache_entry e = *i; cache.erase(i); cache.push_back(e); } } bool t_auth::auth_failed(t_request *r, const t_challenge &c, bool proxy) const { if (c.digest_challenge.stale) { log_file->write_report("Stale nonce value.", "t_auth::auth_failed"); return false; } if (proxy) { return r->hdr_proxy_authorization.contains( c.digest_challenge.realm, r->uri); } else { return r->hdr_authorization.contains( c.digest_challenge.realm, r->uri); } } void t_auth::remove_credentials(t_request *r, const t_challenge &c, bool proxy) const { if (proxy) { r->hdr_proxy_authorization.remove_credentials( c.digest_challenge.realm, r->uri); } else { r->hdr_authorization.remove_credentials( c.digest_challenge.realm, r->uri); } } t_auth::t_auth() { re_register = false; } bool t_auth::authorize(t_user *user_config, t_request *r, t_response *resp) { string username; string passwd; list::iterator i; t_challenge c; bool proxy; assert(resp->must_authenticate()); if (resp->code == R_401_UNAUTHORIZED) { c = resp->hdr_www_authenticate.challenge; proxy = false; } else { c = resp->hdr_proxy_authenticate.challenge; proxy = true; } // Only DIGEST is supported if (cmp_nocase(c.auth_scheme, AUTH_DIGEST) != 0) { log_file->write_header("t_auth::authorize"); log_file->write_raw("Unsupported authentication scheme: "); log_file->write_raw(c.auth_scheme); log_file->write_endl(); log_file->write_footer(); return false; } const t_digest_challenge &dc = c.digest_challenge; i = find_cache_entry(r->uri, dc.realm, proxy); if (auth_failed(r, c, proxy)) { // The current credentials are wrong. Remove them and // ask the user for a username and password. remove_credentials(r, c, proxy); } else { // Determine user name and password if (i != cache.end()) { username = i->credentials.digest_response.username; passwd = i->passwd; } else if (dc.realm == user_config->get_auth_realm() || user_config->get_auth_realm() == "") { username = user_config->get_auth_name(); passwd = user_config->get_auth_pass(); } if (dc.stale) { // The current credentials are stale. Remove them. remove_credentials(r, c, proxy); } } // Ask user for username/password if ((username == "" || passwd == "") && !re_register) { if (!ui->cb_ask_credentials(user_config, dc.realm, username, passwd)) { log_file->write_report("Asking user name and password failed.", "t_auth::authorize"); return false; } } // No valid username/passwd if (username == "" && passwd == "") { log_file->write_report("Incorrect user name and/or password.", "t_auth::authorize"); return false; } bool auth_success; string fail_reason; if (!proxy) { t_credentials cr; auth_success = r->www_authorize(c, user_config, username, passwd, 1, NEW_CNONCE, cr, fail_reason); if (auth_success) { update_cache(r->uri, cr, passwd, proxy); } } else { t_credentials cr; auth_success = r->proxy_authorize(c, user_config, username, passwd, 1, NEW_CNONCE, cr, fail_reason); if (auth_success) { update_cache(r->uri, cr, passwd, proxy); } } if (!auth_success) { log_file->write_report(fail_reason, "t_auth::authorize"); return false; } return true; } void t_auth::remove_from_cache(const string &realm) { if (realm.empty()) { cache.clear(); } else { list::iterator i = find_cache_entry(t_url(), realm); if (i != cache.end()) { cache.erase(i); } } } void t_auth::set_re_register(bool on) { re_register = on; } bool t_auth::get_re_register(void) const { return re_register; } twinkle-1.10.1/src/auth.h000066400000000000000000000111601277565361200151650ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * SIP authentication */ #ifndef _AUTH_H #define _AUTH_H #include "parser/credentials.h" #include "parser/request.h" #include "sockets/url.h" #include using namespace std; /** Size of the credentials cache. */ #define AUTH_CACHE_SIZE 50 /** Credentials cache entry. */ class t_cr_cache_entry { public: /** * Destination for which credentials are cached. * This is not used for the SIP authentication itself. */ t_url to; /** The credentials. */ t_credentials credentials; /** Password. */ string passwd; /** Indicates if proxy authentication was requested. */ bool proxy; /** Constructor. */ t_cr_cache_entry(const t_url &_to, const t_credentials &_cr, const string &_passwd, bool _proxy); }; /** An object of this class authorizes a request given some credentials. */ class t_auth { private: /** Indicates if the current registration request is a re-REGISTER. */ bool re_register; /** * LRU cache credentials for a destination. * The first entry in the list is the least recently used. */ list cache; /** * Find a cache entry that matches the realm. * @param _to [in] Destination for which authentication is needed. * @param realm [in] The authentication realm. * @param proxy [in] Indicates if proxy authentication was requested. * @return An iterator to the cached credentials if found. * @return The end iterator if not found. */ list::iterator find_cache_entry(const t_url &_to, const string &realm, bool proxy=false); /** * Update cached credentials. * If the cache does not contain the credentials already * then it will be added to the end of the list. If the cache * already contains the maximum number of entries, then the least * recently used entry will be removed. * If the cache already contains an entry for credentials, then * this entry will be moved to the end of the list. * @param to [in] Destination for which authentication is needed. * @param cr [in] Credentials to update. * @param passwd [in] The password to store. * @param proxy Indicates if proxy authentication was requested. */ void update_cache(const t_url &to, const t_credentials &cr, const string &passwd, bool proxy); /** * Check if authorization failed. * Authorization failed if the challenge is for a realm for which * the request already contains an authorization header and the * challenge is not stale. * @return true, if authorization failed. * @return false, otherwise. */ bool auth_failed(t_request *r, const t_challenge &c, bool proxy=false) const; /** * Remove existing credentials for this challenge from the * authorization or proxy-authorization header. * @param r [in] The request from which the credentials must be removed. * @param c [in] The challenge for which the credentials must be removed. * @param proxy [in] Indicates if proxy authentication was requested. */ void remove_credentials(t_request *r, const t_challenge &c, bool proxy=false) const; public: /** Constructor. */ t_auth(); /** * Authorize the request based on the challenge in the response * @param user_config [in] The user profile. * @param r [in] The request to be authorized. * @param resp [in] The response containing the challenge. * @return true, if authorization succeeds. * @return false, if authorization fails. * @post On successful authorization, the credentials has been added to * the request in the proper header (Authorization or Proxy-Authorization). */ bool authorize(t_user *user_config, t_request *r, t_response *resp); /** * Remove credentials for a particular realm from cache. * @param realm [in] The authentication realm. */ void remove_from_cache(const string &realm); /** * Set the re-REGISTER indication. * @param on [in] Value to set. */ void set_re_register(bool on); /** Get the re-REGISTER indication. */ bool get_re_register(void) const; }; #endif twinkle-1.10.1/src/call_history.cpp000066400000000000000000000267731277565361200172730ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include #include #include "call_history.h" #include "log.h" #include "sys_settings.h" #include "translator.h" #include "userintf.h" #include "util.h" // Call history file #define CALL_HISTORY_FILE "twinkle.ch"; // Field seperator in call history file #define REC_SEPARATOR '|' //////////////////////// // class t_call_record //////////////////////// t_mutex t_call_record::mtx_class; unsigned short t_call_record::next_id = 1; t_call_record::t_call_record() { mtx_class.lock(); id = next_id++; if (next_id == 65535) next_id = 1; mtx_class.unlock(); time_start = 0; time_answer = 0; time_end = 0; invite_resp_code = 0; } void t_call_record::renew() { t_mutex_guard x(mutex); mtx_class.lock(); id = next_id++; if (next_id == 65535) next_id = 1; mtx_class.unlock(); time_start = 0; time_answer = 0; time_end = 0; direction = DIR_IN; from_display.clear(); from_uri.set_url(""); from_organization.clear(); to_display.clear(); to_uri.set_url(""); to_organization.clear(); reply_to_display.clear(); reply_to_uri.set_url(""); referred_by_display.clear(); referred_by_uri.set_url(""); subject.clear(); rel_cause = CS_LOCAL_USER; invite_resp_code = 0; invite_resp_reason.clear(); far_end_device.clear(); user_profile.clear(); } void t_call_record::start_call(const t_request *invite, t_direction dir, const string &_user_profile) { assert(invite->method == INVITE); t_mutex_guard x(mutex); struct timeval t; gettimeofday(&t, NULL); time_start = t.tv_sec; from_display = invite->hdr_from.get_display_presentation(); from_uri = invite->hdr_from.uri; if (invite->hdr_organization.is_populated()) { from_organization = invite->hdr_organization.name; } to_display = invite->hdr_to.display; to_uri = invite->hdr_to.uri; if (invite->hdr_reply_to.is_populated()) { reply_to_display = invite->hdr_reply_to.display; reply_to_uri = invite->hdr_reply_to.uri; } if (invite->hdr_referred_by.is_populated()) { referred_by_display = invite->hdr_referred_by.display; referred_by_uri = invite->hdr_referred_by.uri; } if (invite->hdr_subject.is_populated()) { subject = invite->hdr_subject.subject; } direction = dir; user_profile = _user_profile; if (direction == DIR_IN && invite->hdr_user_agent.is_populated()) { far_end_device = invite->hdr_user_agent.get_ua_info(); } } void t_call_record::fail_call(const t_response *resp) { assert(resp->get_class() >= 3); assert(resp->hdr_cseq.method == INVITE); t_mutex_guard x(mutex); struct timeval t; gettimeofday(&t, NULL); time_end = t.tv_sec; rel_cause = CS_FAILURE; invite_resp_code = resp->code; invite_resp_reason = resp->reason; if (resp->hdr_organization.is_populated()) { to_organization = resp->hdr_organization.name; } if (direction == DIR_OUT && resp->hdr_server.is_populated()) { far_end_device = resp->hdr_server.get_server_info(); } } void t_call_record::answer_call(const t_response *resp) { assert(resp->is_success()); t_mutex_guard x(mutex); struct timeval t; gettimeofday(&t, NULL); time_answer = t.tv_sec; invite_resp_code = resp->code; invite_resp_reason = resp->reason; if (resp->hdr_organization.is_populated()) { to_organization = resp->hdr_organization.name; } if (direction == DIR_OUT && resp->hdr_server.is_populated()) { far_end_device = resp->hdr_server.get_server_info(); } } void t_call_record::end_call(t_rel_cause cause) { struct timeval t; t_mutex_guard x(mutex); gettimeofday(&t, NULL); time_end = t.tv_sec; rel_cause = cause; } void t_call_record::end_call(bool far_end) { if (far_end) { end_call(CS_REMOTE_USER); } else { end_call(CS_LOCAL_USER); } } string t_call_record::get_rel_cause(void) const { switch (rel_cause) { case CS_LOCAL_USER: return TRANSLATE2("CoreCallHistory", "local user"); case CS_REMOTE_USER: return TRANSLATE2("CoreCallHistory", "remote user"); case CS_FAILURE: return TRANSLATE2("CoreCallHistory", "failure"); } return TRANSLATE2("CoreCallHistory", "unknown"); } string t_call_record::get_rel_cause_internal(void) const { switch (rel_cause) { case CS_LOCAL_USER: return "local user"; case CS_REMOTE_USER: return "remote user"; case CS_FAILURE: return "failure"; } return "unknown"; } string t_call_record::get_direction(void) const { switch (direction) { case DIR_IN: return TRANSLATE2("CoreCallHistory", "in"); case DIR_OUT: return TRANSLATE2("CoreCallHistory", "out"); } return TRANSLATE2("CoreCallHistory", "unknown"); } string t_call_record::get_direction_internal(void) const { switch (direction) { case DIR_IN: return "in"; case DIR_OUT: return "out"; } return "unknown"; } bool t_call_record::set_rel_cause(const string &cause) { // NOTE: caller and callee were used before version 0.7 // They are still checked here for backward compatibility if (cause == "caller" || cause == "local user") { rel_cause = CS_LOCAL_USER; } else if (cause == "callee" || cause == "remote user") { rel_cause = CS_REMOTE_USER; } else if (cause == "failure") { rel_cause = CS_FAILURE; } else { return false; } return true; } bool t_call_record::set_direction(const string &dir) { if (dir == "in") { direction = DIR_IN; } else if (dir == "out") { direction = DIR_OUT; } else { return false; } return true; } bool t_call_record::create_file_record(vector &v) const { v.clear(); v.push_back(ulong2str(time_start)); v.push_back(ulong2str(time_answer)); v.push_back(ulong2str(time_end)); v.push_back(get_direction_internal()); v.push_back(from_display); v.push_back(from_uri.encode()); v.push_back(from_organization); v.push_back(to_display); v.push_back(to_uri.encode()); v.push_back(to_organization); v.push_back(reply_to_display); v.push_back(reply_to_uri.encode()); v.push_back(referred_by_display); v.push_back(referred_by_uri.encode()); v.push_back(subject); v.push_back(get_rel_cause_internal()); v.push_back(int2str(invite_resp_code)); v.push_back(invite_resp_reason); v.push_back(far_end_device); v.push_back(user_profile); return true; } bool t_call_record::populate_from_file_record(const vector &v) { t_mutex_guard x(mutex); // Check number of fields if (v.size() != 20) return false; time_start = std::stoul(v[0], NULL, 10); time_answer = std::stoul(v[1], NULL, 10); time_end = std::stoul(v[2], NULL, 10); if (!set_direction(v[3])) return false; from_display = v[4]; from_uri.set_url(v[5]); if (!from_uri.is_valid()) return false; from_organization = v[6]; to_display = v[7]; to_uri.set_url(v[8]); if (!to_uri.is_valid()) return false; to_organization = v[9]; reply_to_display = v[10]; reply_to_uri.set_url(v[11]); referred_by_display = v[12]; referred_by_uri.set_url(v[13]); subject = v[14]; if (!set_rel_cause(v[15])) return false; invite_resp_code = atoi(v[16].c_str()); invite_resp_reason = v[17]; far_end_device = v[18]; user_profile = v[19]; return true; } bool t_call_record::is_valid(void) const { if (time_start == 0 || time_end == 0) return false; if (time_answer > 0 && rel_cause == CS_FAILURE) return false; return true; } unsigned short t_call_record::get_id(void) const { return id; } t_call_record::t_call_record(const t_call_record& that) { *this = that; } t_call_record& t_call_record::operator=(const t_call_record& that) { t_mutex_guard x1(that.mutex); t_mutex_guard x2(this->mutex); id = that.id; time_start = that.time_start; time_answer = that.time_answer; time_end = that.time_end; direction = that.direction; from_display = that.from_display; from_uri = that.from_uri; from_organization = that.from_organization; to_display = that.to_display; to_uri = that.to_uri; to_organization = that.to_organization; reply_to_display = that.reply_to_display; reply_to_uri = that.reply_to_uri; referred_by_display = that.referred_by_display; referred_by_uri = that.referred_by_uri; subject = that.subject; rel_cause = that.rel_cause; invite_resp_code = that.invite_resp_code; invite_resp_reason = that.invite_resp_reason; far_end_device = that.far_end_device; user_profile = that.user_profile; return *this; } //////////////////////// // class t_call_history //////////////////////// t_call_history::t_call_history() : utils::t_record_file() { set_header("time_start|time_answer|time_end|direction|from_display|from_uri|" "from_organization|to_display|to_uri|to_organization|" "reply_to_display|reply_to_uri|referred_by_display|referred_by_uri|" "subject|rel_cause|invite_resp_code|invite_resp_reason|" "far_end_device|user_profile"); set_separator(REC_SEPARATOR); string s(DIR_HOME); s += "/"; s += USER_DIR; s += "/"; s += CALL_HISTORY_FILE; set_filename(s); num_missed_calls = 0; } void t_call_history::add_call_record(const t_call_record &call_record, bool write) { if (!call_record.is_valid()) { log_file->write_report("Call history record is not valid.", "t_call_history::add_call_record", LOG_NORMAL, LOG_WARNING); return; } mtx_records.lock(); records.push_back(call_record); while (records.size() > (size_t)sys_config->get_ch_max_size()) { records.pop_front(); } // Increment missed calls counter if (call_record.rel_cause == t_call_record::CS_FAILURE && call_record.direction == t_call_record::DIR_IN) { ++num_missed_calls; ui->cb_missed_call(num_missed_calls); } mtx_records.unlock(); if (write) { string msg; if (!save(msg)) { log_file->write_report(msg, "t_call_history::add_call_record", LOG_NORMAL, LOG_WARNING); } } // Update call history in user interface. ui->cb_call_history_updated(); } void t_call_history::delete_call_record(unsigned short id, bool write) { mtx_records.lock(); for (list::iterator i = records.begin(); i != records.end(); i++) { if (i->get_id() == id) { records.erase(i); break; } } mtx_records.unlock(); if (write) { string msg; if (!save(msg)) { log_file->write_report(msg, "t_call_history::delete_call_record", LOG_NORMAL, LOG_WARNING); } } // Update call history in user interface. ui->cb_call_history_updated(); } void t_call_history::get_history(list &history) { mtx_records.lock(); history = records; mtx_records.unlock(); } void t_call_history::clear(bool write) { mtx_records.lock(); records.clear(); mtx_records.unlock(); if (write) { string msg; if (!save(msg)) { log_file->write_report(msg, "t_call_history::clear", LOG_NORMAL, LOG_WARNING); } } // Update call history in user interface. ui->cb_call_history_updated(); clear_num_missed_calls(); } int t_call_history::get_num_missed_calls(void) const { return num_missed_calls; } void t_call_history::clear_num_missed_calls(void) { mtx_records.lock(); num_missed_calls = 0; mtx_records.unlock(); ui->cb_missed_call(0); } twinkle-1.10.1/src/call_history.h000066400000000000000000000151061277565361200167240ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Call history */ #ifndef _CALL_HISTORY_H #define _CALL_HISTORY_H #include #include #include #include "parser/request.h" #include "parser/response.h" #include "sockets/url.h" #include "threads/mutex.h" #include "utils/record_file.h" using namespace std; /** Call detail record. */ class t_call_record : public utils::t_record { public: /** Release cause of a call. */ enum t_rel_cause { CS_LOCAL_USER, /**< Released by the local user. */ CS_REMOTE_USER, /**< Released by the remote user. */ CS_FAILURE /**< Call ended due to failure. */ }; /** Direction of the call as seen from the user. */ enum t_direction { DIR_IN, /**< Incoming call. */ DIR_OUT /**< Outgoing call. */ }; private: static t_mutex mtx_class; /**< Protect static members. */ static unsigned short next_id; /**< Next id to be used. */ unsigned short id; /**< Record id. */ public: time_t time_start; /**< Timestamp of start of call. */ time_t time_answer; /**< Timestamp when call got answered. */ time_t time_end; /**< Timestamp of end of call. */ t_direction direction; string from_display; t_url from_uri; string from_organization; string to_display; t_url to_uri; string to_organization; string reply_to_display; t_url reply_to_uri; string referred_by_display; t_url referred_by_uri; string subject; t_rel_cause rel_cause; int invite_resp_code; /**< Response code sent/received on INVITE. */ string invite_resp_reason; /**< Response reason sent/received on INVITE. */ string far_end_device; /**< User-agent/Server description of device. */ string user_profile; /** Constructor. */ t_call_record(); /** Copy constructor */ t_call_record(const t_call_record& that); /** Assignment operator */ t_call_record& operator=(const t_call_record& that); /** * Clear current settings and get a new record id. * So this action creates a brand new call record. */ void renew(); /** * Record call start. * @param invite [in] The INVITE request starting the call. * @param dir [in] Call direction. * @param _user_profile [in] The user profile. */ void start_call(const t_request *invite, t_direction dir, const string &_user_profile); /** * Record call failure. This is also the end of the call. * @param resp [in] The failure response. */ void fail_call(const t_response *resp); /** * Record successful call answer. * @param resp [in] The 2XX INVITE response. */ void answer_call(const t_response *resp); /** * Record end of a successful call with an explicit cause. * @param cause [in] The release cause. */ void end_call(t_rel_cause cause); /** * Record end of a successful call. * If far_end is true, then the far-end ended the call, otherwise * the near-end ended the call. This indication together with the * direction determines the correct cause of the call end. * @param far_end [in] Indicates if the far end released the call. */ void end_call(bool far_end); /** * Get user presentable release cause description. * The release cause is returned in the language of the user. * @return Release cause description. */ string get_rel_cause(void) const; /** * Get release cause description for internal use. * This description is written to file. * @return Release cause description. */ string get_rel_cause_internal(void) const; /** * Get user presentable direction description. * The description is returned in the language of the user. * @return Direction description. */ string get_direction(void) const; /** * Get direction description for internal use. * This description is written to file. * @return Direction description. */ string get_direction_internal(void) const; /** * Set the release cause from an internal description. * @param cause [in] Internal release cause description. * @return Indication if operation succeeded. */ bool set_rel_cause(const string &cause); /** * Set the direction from an internal description. * @param cause [in] Internal direction description. * @return Indication if operation succeeded. */ bool set_direction(const string &dir); virtual bool create_file_record(vector &v) const; virtual bool populate_from_file_record(const vector &v); /** * Check if this call record represents a valid call. * @return Indication if call record is valid. */ bool is_valid(void) const; /** Get the record id. */ unsigned short get_id(void) const; private: // Guarded by a mutex, because contents of this class are updated from other threads. // The main thread always creates a copy (snapshot) of current state. mutable t_mutex mutex; }; /** History of calls. */ class t_call_history : public utils::t_record_file { private: /** Number of missed calls since this counter was cleared. */ int num_missed_calls; public: /** Constructor. */ t_call_history(); /** * Add a call record to the history. * @param call_record [in] The call record to be added. * @param write [in] Indicates if history must be written to file after adding. */ void add_call_record(const t_call_record &call_record, bool write = true); /** * Delete record with a given id. * @param id [in] The record id that must be deleted. * @param write [in] Indicates if history must be written to file after deleting. */ void delete_call_record(unsigned short id, bool write = true); /** * Get list of historic call records. * @param history [out] List of historic call records. */ void get_history(list &history); /** * Clear call history file. * @param write [in] Indicates if history must be written to file after adding. */ void clear(bool write = true); /** Get number of missed calls. */ int get_num_missed_calls(void) const; /** Clear number of missed calls. */ void clear_num_missed_calls(void); }; extern t_call_history *call_history; #endif twinkle-1.10.1/src/call_script.cpp000066400000000000000000000303461277565361200170650ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include "call_script.h" #include "log.h" #include "userintf.h" #include "util.h" // Maximum length of the reason value #define MAX_LEN_REASON 50 // Script result fields #define SCR_ACTION "action" #define SCR_REASON "reason" #define SCR_CONTACT "contact" #define SCR_CALLER_NAME "caller_name" #define SCR_RINGTONE "ringtone" #define SCR_DISPLAY_MSG "display_msg" #define SCR_INTERNAL_ERROR "internal_error" // Script triggers #define SCR_TRIGGER_IN_CALL "in_call" #define SCR_TRIGGER_IN_CALL_ANSWERED "in_call_answered" #define SCR_TRIGGER_IN_CALL_FAILED "in_call_failed" #define SCR_TRIGGER_OUT_CALL "out_call" #define SCR_TRIGGER_OUT_CALL_ANSWERED "out_call_answered" #define SCR_TRIGGER_OUT_CALL_FAILED "out_call_failed" #define SCR_TRIGGER_LOCAL_RELEASE "local_release" #define SCR_TRIGGER_REMOTE_RELEASE "remote_release" ///////////////////////// // class t_script_result ///////////////////////// t_script_result::t_script_result() { clear(); } t_script_result::t_action t_script_result::str2action(const string action_string) { string s = tolower(action_string); t_action result; if (s == "continue") { result = ACTION_CONTINUE; } else if (s == "reject") { result = ACTION_REJECT; } else if (s == "dnd") { result = ACTION_DND; } else if (s == "redirect") { result = ACTION_REDIRECT; } else if (s == "autoanswer") { result = ACTION_AUTOANSWER; } else { // Unknown action result = ACTION_ERROR; } return result; } void t_script_result::clear(void) { action = ACTION_CONTINUE; reason.clear(); contact.clear(); caller_name.clear(); ringtone.clear(); display_msgs.clear(); } void t_script_result::set_parameter(const string ¶meter, const string &value) { if (parameter == SCR_ACTION) { action = str2action(value); } else if (parameter == SCR_REASON) { if (value.size() <= MAX_LEN_REASON) { reason = value; } else { reason = value.substr(0, MAX_LEN_REASON); } } else if (parameter == SCR_CONTACT) { contact = value; } else if (parameter == SCR_CALLER_NAME) { caller_name = value; } else if (parameter == SCR_RINGTONE) { ringtone = value; } else if (parameter == SCR_DISPLAY_MSG) { display_msgs.push_back(value); } // Unknown parameters are ignored } ///////////////////////// // class t_call_script ///////////////////////// string t_call_script::trigger2str(t_trigger t) const { switch (t) { case TRIGGER_IN_CALL: return SCR_TRIGGER_IN_CALL; case TRIGGER_IN_CALL_ANSWERED: return SCR_TRIGGER_IN_CALL_ANSWERED; case TRIGGER_IN_CALL_FAILED: return SCR_TRIGGER_IN_CALL_FAILED; case TRIGGER_OUT_CALL: return SCR_TRIGGER_OUT_CALL; case TRIGGER_OUT_CALL_ANSWERED: return SCR_TRIGGER_OUT_CALL_ANSWERED; case TRIGGER_OUT_CALL_FAILED: return SCR_TRIGGER_OUT_CALL_FAILED; case TRIGGER_LOCAL_RELEASE: return SCR_TRIGGER_LOCAL_RELEASE; case TRIGGER_REMOTE_RELEASE: return SCR_TRIGGER_REMOTE_RELEASE; default: return "unknown"; } } char **t_call_script::create_env(t_sip_message *m) const { string var_twinkle; // Number of existing environment variables int environ_size = 0; for (int i = 0; environ[i] != NULL; i++) { environ_size++; } // Number of SIP environment variables int start_sip_env = environ_size; // Position of SIP variables list l = m->encode_env(); var_twinkle = "SIP_FROM_USER="; var_twinkle += m->hdr_from.uri.get_user(); l.push_back(var_twinkle); var_twinkle = "SIP_FROM_HOST="; var_twinkle += m->hdr_from.uri.get_host(); l.push_back(var_twinkle); var_twinkle = "SIP_TO_USER="; var_twinkle += m->hdr_to.uri.get_user(); l.push_back(var_twinkle); var_twinkle = "SIP_TO_HOST="; var_twinkle += m->hdr_to.uri.get_host(); l.push_back(var_twinkle); environ_size += l.size(); // Number of Twinkle environment variables int start_twinkle_env = environ_size; // Position of Twinkle variables environ_size += 3; // MEMMAN not called on purpose char **env = new char *[environ_size + 1]; // Copy current environment to child for (int i = 0; environ[i] != NULL; i++) { env[i] = strdup(environ[i]); } // Add environment variables for SIP request int j = start_sip_env; for (list::iterator i = l.begin(); i != l.end(); i++, j++) { env[j] = strdup(i->c_str()); } // Add Twinkle specific environment variables var_twinkle = "TWINKLE_USER_PROFILE="; var_twinkle += user_config->get_profile_name(); env[start_twinkle_env] = strdup(var_twinkle.c_str()); var_twinkle = "TWINKLE_TRIGGER="; var_twinkle += trigger2str(trigger); env[start_twinkle_env + 1] = strdup(var_twinkle.c_str()); var_twinkle = "TWINKLE_LINE="; var_twinkle += ulong2str(line_number); env[start_twinkle_env + 2] = strdup(var_twinkle.c_str()); // Terminate array with NULL env[environ_size] = NULL; return env; } char **t_call_script::create_argv(void) const { // Determine script agument list vector arg_list = split_ws(script_command, true); // MEMMAN not called on purpose char **argv = new char *[arg_list.size() + 1]; int idx = 0; for (vector::iterator i = arg_list.begin(); i != arg_list.end(); i++, idx++) { argv[idx] = strdup(i->c_str()); } argv[arg_list.size()] = NULL; return argv; } t_call_script::t_call_script(t_user *_user_config, t_trigger _trigger, uint16 _line_number) : user_config(_user_config), trigger(_trigger), line_number(_line_number) { switch (trigger) { case TRIGGER_IN_CALL: script_command = user_config->get_script_incoming_call(); break; case TRIGGER_IN_CALL_ANSWERED: script_command = user_config->get_script_in_call_answered(); break; case TRIGGER_IN_CALL_FAILED: script_command = user_config->get_script_in_call_failed(); break; case TRIGGER_OUT_CALL: script_command = user_config->get_script_outgoing_call(); break; case TRIGGER_OUT_CALL_ANSWERED: script_command = user_config->get_script_out_call_answered(); break; case TRIGGER_OUT_CALL_FAILED: script_command = user_config->get_script_out_call_failed(); break; case TRIGGER_LOCAL_RELEASE: script_command = user_config->get_script_local_release(); break; case TRIGGER_REMOTE_RELEASE: script_command = user_config->get_script_remote_release(); break; default: script_command.clear(); break; } } void t_call_script::exec_action(t_script_result &result, t_sip_message *m) const { result.clear(); if (script_command.empty()) return; log_file->write_header("t_call_script::exec_action"); log_file->write_raw("Execute script: "); log_file->write_raw(script_command); log_file->write_raw("\nTrigger: "); log_file->write_raw(trigger2str(trigger)); log_file->write_raw("\nLine: "); log_file->write_raw(line_number); log_file->write_endl(); log_file->write_footer(); // Create pipe for communication with child process int fds[2]; if (pipe(fds) == -1) { // Failed to create pipe log_file->write_header("t_call_script::exec_action", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Failed to create pipe: "); log_file->write_raw(get_error_str(errno)); log_file->write_endl(); log_file->write_footer(); return; } // Fork child process pid_t pid = fork(); if (pid == -1) { // Failed to fork child process log_file->write_header("t_call_script::exec_action", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Failed to fork child process: "); log_file->write_raw(get_error_str(errno)); log_file->write_endl(); log_file->write_footer(); close(fds[0]); close(fds[1]); return; } else if (pid == 0) { // Child process // Close the read end of the pipe close(fds[0]); // Redirect stdout to the write end of the pipe dup2(fds[1], STDOUT_FILENO); // NOTE: MEMMAN audits are not called as all pointers will be deleted // automatically when the child process dies // Also, the child process has a copy of the MEMMAN object char **argv = create_argv(); // Determine environment char **env = create_env(m); // Replace the child process by the script if (execve(argv[0], argv, env) == -1) { // Failed to execute script. Report error to parent. string err_msg; err_msg = get_error_str(errno); err_msg += ": "; err_msg += argv[0]; cout << SCR_INTERNAL_ERROR << '=' << err_msg << endl; exit(0); } } else { // Parent process log_file->write_header("t_call_script::exec_action"); log_file->write_raw("Child process spawned, pid = "); log_file->write_raw((int)pid); log_file->write_endl(); log_file->write_footer(); // Close the write end of the pipe close(fds[1]); // Read the script results FILE *fp_result = fdopen(fds[0], "r"); if (!fp_result) { log_file->write_header("t_call_script::exec_action", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Failed to open pipe to child: "); log_file->write_raw(get_error_str(errno)); log_file->write_endl(); log_file->write_footer(); // Child will be cleaned up by phone_sigwait close(fds[0]); return; } char *line_buf = NULL; size_t line_buf_len = 0; ssize_t num_read; // Read and parse script results. while ((num_read = getline(&line_buf, &line_buf_len, fp_result)) != -1) { // Strip newline if present if (line_buf[num_read - 1] == '\n') { line_buf[num_read - 1] = 0; } // Convert the read line to a C++ string string line(line_buf); line = trim(line); // Stop reading on end command if (line == "end") break; // Skip empty lines if (line.empty()) continue; // Skip comment lines if (line[0] == '#') continue; vector v = split_on_first(line, '='); // SKip invalid lines if (v.size() != 2) continue; string parameter = trim(v[0]); string value = trim(v[1]); if (parameter == SCR_INTERNAL_ERROR) { log_file->write_report(value, "t_call_script::exec_action", LOG_NORMAL, LOG_WARNING); ui->cb_display_msg(value, MSG_WARNING); result.clear(); break; } result.set_parameter(parameter, value); } if (line_buf) free(line_buf); fclose(fp_result); close(fds[0]); // Child will be cleaned up by phone_sigwait } } void t_call_script::exec_notify(t_sip_message *m) const { if (script_command.empty()) return; log_file->write_header("t_call_script::exec_notify"); log_file->write_raw("Execute script: "); log_file->write_raw(script_command); log_file->write_raw("\nTrigger: "); log_file->write_raw(trigger2str(trigger)); log_file->write_endl(); log_file->write_footer(); // Fork child process pid_t pid = fork(); if (pid == -1) { // Failed to fork child process log_file->write_header("t_call_script::exec_notify", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Failed to fork child process: "); log_file->write_raw(get_error_str(errno)); log_file->write_endl(); log_file->write_footer(); return; } else if (pid == 0) { // Child process // NOTE: MEMMAN audits are not called as all pointers will be deleted // automatically when the child process dies // Also, the child process has a copy of the MEMMAN object char **argv = create_argv(); // Determine environment char **env = create_env(m); // Replace the child process by the script if (execve(argv[0], argv, env) == -1) { // Failed to execute script. exit(0); } } else { // Parent process log_file->write_header("t_call_script::exec_notify"); log_file->write_raw("Child process spawned, pid = "); log_file->write_raw((int)pid); log_file->write_endl(); log_file->write_footer(); // No interaction with child needed. // Child will be cleaned up by phone_sigwait } } twinkle-1.10.1/src/call_script.h000066400000000000000000000146221277565361200165310ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Call scripting interface. * A call script is called by Twinkle during call processing. * Currently only when a call comes in (INVITE received). * Twinkle calls the script and based of the output of the script, the * call is further handled. * * The following environment variables are passed to the script: * @verbatim TWINKLE_USER_PROFILE= TWINKLE_TRIGGER= TWINKLE_LINE= SIPREQUEST_METHOD= SIPREQUEST_URI= SIPSTATUS_CODE= SIPSTATUS_REASON= SIP_FROM_USER= SIP_FROM_HOST= SIP_TO_USER= SIP_TO_HOST= SIP_=
@endverbatim * * The header name is in capitals and dashed are replaced by underscores * * The script can return on stdout how the call should be further * processed. The following output parameters are recognized: * @verbatim action=[continue|reject|dnd|redirect] reason=, for reject and dnd actions contact=, for redirect ation ringtone=, for continue action caller_name= display_msg= (may occur multiple times) end This parameter makes Twinkle stop waiting for the script to complete. @endverbatim * * If no action is returned, the "continue" action is performed. * Invalid output will be skipped. */ #ifndef _H_CALL_SCRIPT #define _H_CALL_SCRIPT #include #include #include "user.h" #include "parser/request.h" using namespace std; /** Results of the incoming call script. */ class t_script_result { public: /** Action to perform. */ enum t_action { ACTION_CONTINUE, /**< Continue with incoming call */ ACTION_REJECT, /**< Reject incoming call with 603 response */ ACTION_DND, /**< Do not disturb, send 480 response */ ACTION_REDIRECT, /**< Redirect call (302 response) */ ACTION_AUTOANSWER, /**< Auto answer incoming call */ ACTION_ERROR /**< Fail call due to error (500 response) */ }; /** @name Output parameters */ //@{ t_action action; /**< How to proceed with call */ string reason; /**< Reason if call is not continued */ string contact; /**< Redirect destination for redirect action */ string caller_name; /**< Name of caller (can be used to override display name) */ string ringtone; /**< Wav file for ring tone */ vector display_msgs; /**< Message (multi line) to show on display */ //@} /** Constructor. */ t_script_result(); /** * Convert string representation to an action. * @param action_string [in] String representation of an action. * @return The action. */ static t_action str2action(const string action_string); /** Clear the results. */ void clear(void); /** * Set output parameter from values read from the result output of a script. * @param parameter [in] Name of the parameter to set, * @param value [in] The value to set. */ void set_parameter(const string ¶meter, const string &value); }; /** Call script definition. */ class t_call_script { public: /** Trigger type. */ enum t_trigger { TRIGGER_IN_CALL, /**< Incoming call. */ TRIGGER_IN_CALL_ANSWERED, /**< Incoming call answered. */ TRIGGER_IN_CALL_FAILED, /**< Incoming call failed. */ TRIGGER_OUT_CALL, /**< Outgoing call made. */ TRIGGER_OUT_CALL_ANSWERED, /**< Outgoing call answered. */ TRIGGER_OUT_CALL_FAILED, /**< Outgoing call failed. */ TRIGGER_LOCAL_RELEASE, /**< Call released by local party. */ TRIGGER_REMOTE_RELEASE /**< Call released by remotre party. */ }; private: t_user *user_config; /**< The user profile. */ string script_command; /**< The script to execute. */ t_trigger trigger; /**< Trigger point for this script. */ /** * Number of the line associated with the call causing the trigger. * The line numbers start at 1. For some triggers a line number does not * apply, e.g. incoming call and all lines are busy. In that case the * line number is 0. */ uint16 line_number; /** * Convert a trigger type value to a string. * @param t [in] Trigger * @return String representation for the trigger. */ string trigger2str(t_trigger t) const; /** * Create environment for the process running the script. * The environment contains the header values of a SIP message. * @param m [in] The SIP message. * @return The environment. * @note This function creates the env array without registering * the memory allocation to MEMMAN. */ char **create_env(t_sip_message *m) const; /** * Create script command argument list. * @return The argument list. * @note This function creates the argv array without registering * the memory allocation to MEMMAN. */ char **create_argv(void) const; protected: /** Cannot use this constructor. */ t_call_script() {}; public: /** * Constructor. * @param _user_config [in] User profile associated with the trigger. * @param _trigger [in] The trigger type. * @param _line_number [in] Line associated with the trigger (0 if no line * is associated). */ t_call_script(t_user *_user_config, t_trigger _trigger, uint16 _line_number); /** * Execute call script resulting in an action. * @param result [out] Contains the result on return. * @param m [in] The SIP message triggering this call script. */ void exec_action(t_script_result &result, t_sip_message *m) const; /** * Execute notification call script. * @param m [in] The SIP message triggering this call script. */ void exec_notify(t_sip_message *m) const; }; #endif twinkle-1.10.1/src/client_request.cpp000066400000000000000000000054241277565361200176130ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "client_request.h" #include "audits/memman.h" t_mutex t_client_request::mtx_next_tuid; t_tuid t_client_request::next_tuid = 1; t_client_request::t_client_request(t_user *user, t_request *r, const t_tid _tid) : redirector(r->uri, user->get_max_redirections()) { request = (t_request *)r->copy(); stun_request = NULL; tid = _tid; ref_count = 1; mtx_next_tuid.lock(); tuid = next_tuid++; if (next_tuid == 65535) next_tuid = 1; mtx_next_tuid.unlock(); } t_client_request::t_client_request(t_user *user, StunMessage *r, const t_tid _tid) : redirector(t_url(), user->get_max_redirections()) { request = NULL; stun_request = new StunMessage(*r); MEMMAN_NEW(stun_request); tid = _tid; ref_count = 1; mtx_next_tuid.lock(); tuid = next_tuid++; if (next_tuid == 65535) next_tuid = 1; mtx_next_tuid.unlock(); } t_client_request::~t_client_request() { if (request) { MEMMAN_DELETE(request); delete request; } if (stun_request) { MEMMAN_DELETE(stun_request); delete stun_request; } } t_client_request *t_client_request::copy(void) { t_client_request *cr = new t_client_request(*this); MEMMAN_NEW(cr); if (request) { cr->request = (t_request *)request->copy(); } if (stun_request) { cr->stun_request = new StunMessage(*stun_request); MEMMAN_NEW(cr->stun_request); } cr->ref_count = 1; return cr; } t_request *t_client_request::get_request(void) const { return request; } StunMessage *t_client_request::get_stun_request(void) const { return stun_request; } t_tuid t_client_request::get_tuid(void) const { return tuid; } t_tid t_client_request::get_tid(void) const { return tid; } void t_client_request::set_tid(t_tid _tid) { tid = _tid; } void t_client_request::renew(t_tid _tid) { mtx_next_tuid.lock(); tuid = next_tuid++; if (next_tuid == 65535) next_tuid = 1; mtx_next_tuid.unlock(); tid = _tid; } int t_client_request::get_ref_count(void) const { return ref_count; } int t_client_request::inc_ref_count(void) { ref_count++; return ref_count; } int t_client_request::dec_ref_count(void) { ref_count--; return ref_count; } twinkle-1.10.1/src/client_request.h000066400000000000000000000064471277565361200172660ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** @file * Bind request with TU and transaction */ #ifndef _CLIENT_REQUEST_H #define _CLIENT_REQUEST_H #include "protocol.h" #include "redirect.h" #include "user.h" #include "transaction_layer.h" #include "threads/mutex.h" #include "parser/request.h" #include "stun/stun.h" using namespace std; /** Object for storing a request together with its Transaction User id and transaction id. */ class t_client_request { private: static t_mutex mtx_next_tuid; /**< Protect updates on @ref next_tuid */ static t_tuid next_tuid; /**< Next transaction user id to handout. */ // A client request is either a SIP or a STUN request t_request *request; /**< SIP request. */ StunMessage *stun_request; /**< STUN request. */ t_tuid tuid; /**< Transaction user id. */ t_tid tid; /**< Transaction id. */ /** Number of references to this object (#dialogs). */ int ref_count; public: /** Redirector for 3XX redirections. */ t_redirector redirector; /** * Constructor. * A copy of the request is stored in the client_request object. * @param user The user profile of the user sending the request. * @param r SIP request. * @param _tid Transaction id. */ t_client_request(t_user *user, t_request *r, const t_tid _tid); /** * Constructor. * A copy of the request is stored in the client_request object. * @param user The user profile of the user sending the request. * @param r STUN request. * @param _tid Transaction id. */ t_client_request(t_user *user, StunMessage *r, const t_tid _tid); /** Destructor. */ ~t_client_request(); /** * Create a copy of the client request. * @return Copy of the client request. * @note: The request inside the client request is copied. */ t_client_request *copy(void); /** * Get a pointer to the SIP request. * @return Pointer to the SIP request. */ t_request *get_request(void) const; /** * Get a pointer to the STUN request. * @return Pointer to the STUN request. */ StunMessage *get_stun_request(void) const; /** Get the transaction user id. */ t_tuid get_tuid(void) const; /** Get the transaction id. */ t_tid get_tid(void) const; /** Set the transaction id. */ void set_tid(t_tid _tid); /** * Create a new tuid and set tid. * @param _tid The new tid to set. */ void renew(t_tid _tid); /** Get the reference count. */ int get_ref_count(void) const; /** * Increment reference count. * @return The reference count after increment. */ int inc_ref_count(void); /** * Decrement reference count. * @returns The reference count after decrement. */ int dec_ref_count(void); }; #endif twinkle-1.10.1/src/cmd_socket.cpp000066400000000000000000000123021277565361200166710ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include "cmd_socket.h" #include "log.h" #include "sys_settings.h" #include "userintf.h" #include "util.h" #include "audits/memman.h" #include "sockets/socket.h" namespace cmdsocket { /** Command opcodes */ enum t_cmd_code { CMD_CALL, /**< Call */ CMD_CLI, /**< Any CLI command */ CMD_SHOW, /**< Show Twinkle */ CMD_HIDE /**< Hide Twinkle */ }; string cmd_code2str(t_cmd_code opcode) { switch (opcode) { case CMD_CALL: return "CALL"; case CMD_CLI: return "CLI"; case CMD_SHOW: return "SHOW"; case CMD_HIDE: return "HIDE"; default: return "UNKNOWN"; } } void exec_cmd(t_socket_local &sock_client) { t_cmd_code opcode; bool immediate; int len; string log_msg; try { if (sock_client.read(&opcode, sizeof(opcode)) != sizeof(opcode)) { log_file->write_report("Failed to read opcode from socket.", "cmdsocket::exec_cmd", LOG_NORMAL, LOG_WARNING); return; } if (sock_client.read(&immediate, sizeof(immediate)) != sizeof(immediate)) { log_file->write_report("Failed to read immediate mode from socket.", "cmdsocket::exec_cmd", LOG_NORMAL, LOG_WARNING); return; } if (sock_client.read(&len, sizeof(len)) != sizeof(len)) { log_file->write_report("Failed to read length from socket.", "cmdsocket::exec_cmd", LOG_NORMAL, LOG_WARNING); return; } char args[len]; if (sock_client.read(args, len) != len) { log_file->write_report("Failed to read arguments from socket.", "cmdsocket::exec_cmd", LOG_NORMAL, LOG_WARNING); return; } log_file->write_header("cmdsocket::exec_cmd", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("External command received:\n"); log_file->write_raw("Opcode: "); log_file->write_raw(cmd_code2str(opcode)); log_file->write_raw("\nImmediate: "); log_file->write_raw(bool2yesno(immediate)); log_file->write_raw("\nArguments: "); log_file->write_raw(args); log_file->write_endl(); log_file->write_footer(); switch (opcode) { case CMD_CALL: ui->cmd_call(args, immediate); break; case CMD_CLI: ui->cmd_cli(args, immediate); break; case CMD_SHOW: ui->cmd_show(); break; case CMD_HIDE: ui->cmd_hide(); break; default: // Discard unknown commands log_file->write_header("cmdsocket::exec_cmd", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Unknown external command received:\n"); log_file->write_raw("Opcode: "); log_file->write_raw(cmd_code2str(opcode)); log_file->write_raw("\nImmediate: "); log_file->write_raw(bool2yesno(immediate)); log_file->write_raw("\nArguments: "); log_file->write_raw(args); log_file->write_endl(); log_file->write_footer(); break; } } catch (int e) { log_msg = "Failed to read from socket.\n"; log_msg += get_error_str(e); log_msg += "\n"; log_file->write_report(log_msg, "cmdsocket::exec_cmd", LOG_NORMAL, LOG_WARNING); } } void *listen_cmd(void *arg) { t_socket_local *sock_cmd = (t_socket_local *)arg; string log_msg; while (true) { try { int fd = sock_cmd->accept(); t_socket_local sock_client(fd); exec_cmd(sock_client); } catch (int e) { log_msg = "Accept failed on socket.\n"; log_msg += get_error_str(e); log_msg += "\n"; log_file->write_report(log_msg, "cmdsocket::listen_cmd", LOG_NORMAL, LOG_WARNING); return NULL; } } } void write_cmd_to_socket(t_cmd_code opcode, bool immediate, const string &args) { string name = sys_config->get_dir_user(); name += '/'; name += CMD_SOCKNAME; try { t_socket_local sock_cmd; sock_cmd.connect(name); sock_cmd.write(&opcode, sizeof(opcode)); sock_cmd.write(&immediate, sizeof(immediate)); int len = args.size() + 1; sock_cmd.write(&len, sizeof(len)); char *buf = strdup(args.c_str()); MEMMAN_NEW(buf); sock_cmd.write(buf, len); MEMMAN_DELETE(buf); free(buf); } catch (int e) { // This function will be called from Twinkle when it // notices another Twinkle is already running. In that // case this process does not have a log file. So write // errors to stderr cerr << "Failed to send " << cmd_code2str(opcode) << " command to " << name << endl; cerr << get_error_str(e) << endl; } } void cmd_call(const string &destination, bool immediate) { write_cmd_to_socket(CMD_CALL, immediate, destination); } void cmd_cli(const string &cli_command, bool immediate) { write_cmd_to_socket(CMD_CLI, immediate, cli_command); } void cmd_show(void) { write_cmd_to_socket(CMD_SHOW, true, ""); } void cmd_hide(void) { write_cmd_to_socket(CMD_HIDE, true, ""); } } twinkle-1.10.1/src/cmd_socket.h000066400000000000000000000034141277565361200163420ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Twinkle listens on a local socket for external commands. */ #ifndef _H_CMD_SOCKET #define _H_CMD_SOCKET #include /** Name of the local socket. */ #define CMD_SOCKNAME ".cmdsock" using namespace std; namespace cmdsocket { /** * Listen on local socket for commands. * @param arg A local socket (@ref t_socket_local) */ void *listen_cmd(void *arg); /** * Send call command to the local socket. * @param destination The SIP destination to call. * @param immediate Indicates if the call should be made immediately * without asking the user for confirmation. */ void cmd_call(const string &destination, bool immediate); /** * Send a CLI command to the local socket. * @param cli_command The CLI command to send. * @param immediate Indicates if the call should be made immediately * without asking the user for confirmation. */ void cmd_cli(const string &cli_command, bool immediate); /** Send show command to the local socket. */ void cmd_show(void); /** Send hide command to the local socket. */ void cmd_hide(void); } #endif twinkle-1.10.1/src/dialog.cpp000066400000000000000000003140371277565361200160270ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include "call_history.h" #include "call_script.h" #include "dialog.h" #include "exceptions.h" #include "line.h" #include "log.h" #include "phone_user.h" #include "sub_refer.h" #include "util.h" #include "userintf.h" #include "audio/rtp_telephone_event.h" #include "audits/memman.h" #include "im/im_iscomposing_body.h" #include "sdp/sdp.h" #include "sockets/socket.h" #include "stun/stun_transaction.h" extern t_event_queue *evq_sender; extern t_event_queue *evq_trans_mgr; extern string user_host; extern string local_hostname; extern t_phone *phone; // Protected // Create a request within a dialog // RFC 3261 12.2.1.1 t_request *t_dialog::create_request(t_method m) { assert(state != DS_NULL); t_user *user_config = phone_user->get_user_profile(); // RFC 3261 9.1 if (m == CANCEL) { t_request *r = new t_request(m); MEMMAN_NEW(r); assert(req_out_invite); t_request *orig_req = req_out_invite->get_request(); r->hdr_to = orig_req->hdr_to; r->hdr_from = orig_req->hdr_from; r->hdr_call_id = orig_req->hdr_call_id; r->hdr_cseq.set_seqnr(orig_req->hdr_cseq.seqnr); r->hdr_cseq.set_method(CANCEL); r->hdr_via = orig_req->hdr_via; // RFC 3261 8.1.1.7 r->hdr_max_forwards.set_max_forwards(MAX_FORWARDS); r->hdr_route = orig_req->hdr_route; SET_HDR_USER_AGENT(r->hdr_user_agent); r->uri = orig_req->uri; // RFC 3263 4 // CANCEL for a particular SIP request MUST be sent to the same SIP // server that the SIP request was delivered to. t_ip_port ip_port; orig_req->get_destination(ip_port, *user_config); r->set_destination(ip_port); return r; } t_request *r = t_abstract_dialog::create_request(m); // CSeq header if (m == ACK) { assert(req_out_invite); // Local sequence number was incremented by t_abstract_dialog. // Decrement as it ACK does not take a new sequence number. local_seqnr--; // ACK has the same sequence number // as the INVITE. r->hdr_cseq.set_seqnr(req_out_invite->get_request()->hdr_cseq.seqnr); // RFC 3261 22.1 // Authorization and Proxy-Authorization headers in INVITE // must be repeated in ACK r->hdr_authorization = req_out_invite->get_request()-> hdr_authorization; r->hdr_proxy_authorization = req_out_invite->get_request()-> hdr_proxy_authorization; } // Contact header t_contact_param contact; switch (m) { case REFER: case SUBSCRIBE: case NOTIFY: // RFC 3265 7.1, RFC 3515 2.2 // Contact header is mandatory contact.uri.set_url(line->create_user_contact(h_ip2str(r->get_local_ip()))); r->hdr_contact.add_contact(contact); break; default: break; } // Privacy header if (line->get_hide_user()) { r->hdr_privacy.add_privacy(PRIVACY_ID); } return r; } // NULL state. Waiting for incoming INVITE void t_dialog::state_null(t_request *r, t_tuid tuid, t_tid tid) { t_response *resp; t_user *user_config = phone_user->get_user_profile(); if (r->method != INVITE) { state = DS_TERMINATED; return; } // Set local tag if (r->hdr_to.tag.size() == 0) { local_tag = NEW_TAG; } else { local_tag = r->hdr_to.tag; } // If STUN is enabled, then first send a STUN binding request to // discover the IP adderss and port for media. if (phone->use_stun(user_config)) { // The STUN transaction may take a while. // Send 100 Trying resp = r->create_response(R_100_TRYING); resp->hdr_to.set_tag(""); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; if (!stun_bind_media()) { // STUN request failed. Send a 500 on the INVITE. resp = r->create_response(R_500_INTERNAL_SERVER_ERROR); resp->hdr_to.set_tag(local_tag); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; state = DS_TERMINATED; return; } } call_id = r->hdr_call_id.call_id; // Initialize local seqnr local_seqnr = NEW_SEQNR; local_resp_nr = NEW_SEQNR; remote_tag = r->hdr_from.tag; local_uri = r->hdr_to.uri; local_display = r->hdr_to.display; remote_uri = r->hdr_from.uri; remote_display = r->hdr_from.display; // Set remote target URI and display name remote_target_uri = r->hdr_contact.contact_list.front().uri; remote_target_display = r-> hdr_contact.contact_list.front().display; // Set route set if (r->hdr_record_route.is_populated()) { route_set = r->hdr_record_route.route_list; } // RFC 3261 13.2.1 // An initial INVITE should list all supported extensions. // Set supported extensions if (r->hdr_supported.is_populated()) { remote_extensions.insert(r->hdr_supported.features.begin(), r->hdr_supported.features.end()); } // Media information int warn_code; string warn_text; if (r->body) { switch(r->body->get_type()) { case BODY_SDP: if (session->process_sdp_offer((t_sdp*)r->body, warn_code, warn_text)) { session->recvd_offer = true; break; } // Unsupported media resp = r->create_response( R_488_NOT_ACCEPTABLE_HERE); resp->hdr_to.set_tag(local_tag); resp->hdr_warning.add_warning(t_warning(LOCAL_HOSTNAME, 0, warn_code, warn_text)); line->send_response(resp, tuid, tid); // Create call history record line->call_hist_record.start_call(r, t_call_record::DIR_IN, user_config->get_profile_name()); line->call_hist_record.fail_call(resp); MEMMAN_DELETE(resp); delete resp; state = DS_TERMINATED; return; default: // Unsupported body type. Reject call. resp = r->create_response( R_415_UNSUPPORTED_MEDIA_TYPE); resp->hdr_to.set_tag(local_tag); // RFC 3261 21.4.13 SET_HDR_ACCEPT(resp->hdr_accept); // Create call history record line->call_hist_record.start_call(r, t_call_record::DIR_IN, user_config->get_profile_name()); line->call_hist_record.fail_call(resp); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; state = DS_TERMINATED; return; } } resp = r->create_response(R_180_RINGING); resp->hdr_to.set_tag(local_tag); // RFC 3261 13.3.1.1 // A provisional response creates an early dialog, so // copy the Record-Route header and add a Contact // header. // Copy the Record-Route header from request to response if (r->hdr_record_route.is_populated()) { resp->hdr_record_route = r->hdr_record_route; } // Set Contact header t_contact_param contact; contact.uri.set_url(line->create_user_contact(h_ip2str(resp->get_local_ip()))); resp->hdr_contact.add_contact(contact); // RFC 3262 3 // Send 180 response reliable if needed if (r->hdr_require.contains(EXT_100REL) || (r->hdr_supported.contains(EXT_100REL) && (user_config->get_ext_100rel() == EXT_PREFERRED || user_config->get_ext_100rel() == EXT_REQUIRED))) { resp->hdr_require.add_feature(EXT_100REL); resp->hdr_rseq.set_resp_nr(++local_resp_nr); // RFC 3262 5 // Create SDP offer in first reliable response if no offer // was received in INVITE. // This implentation does not create an answer in an // reliable 1xx response if an offer was received. if (!session->recvd_offer) { session->create_sdp_offer(resp, SDP_O_USER); } // Keep a copy of the response for retransmission resp_1xx_invite = (t_response *)resp->copy(); // Start 100rel timeout and guard timers line->start_timer(LTMR_100REL_GUARD, get_object_id()); line->start_timer(LTMR_100REL_TIMEOUT, get_object_id()); } line->send_response(resp, req_in_invite->get_tuid(), req_in_invite->get_tid()); MEMMAN_DELETE(resp); delete resp; ui->cb_incoming_call(user_config, line->get_line_number(), r); line->call_hist_record.start_call(r, t_call_record::DIR_IN, user_config->get_profile_name()); state = DS_W4ANSWER; } // A provisional answer has been sent. Waiting for user to answer. void t_dialog::state_w4answer(t_request *r, t_tuid tuid, t_tid tid) { t_response *resp; t_user *user_config = phone_user->get_user_profile(); bool tear_down = false; bool answer_call = false; t_call_script script_in_call_failed(user_config, t_call_script::TRIGGER_IN_CALL_FAILED, line->get_line_number() + 1); switch (r->method) { case CANCEL: // Cancel the request and terminate the dialog. // A response on the CANCEL is already given by dialog::recvd_cancel resp = req_in_invite->get_request()-> create_response(R_487_REQUEST_TERMINATED); resp->hdr_to.set_tag(local_tag); line->send_response(resp, req_in_invite->get_tuid(), req_in_invite->get_tid()); line->call_hist_record.fail_call(resp); // Trigger call script script_in_call_failed.exec_notify(resp); MEMMAN_DELETE(resp); delete resp; ui->cb_call_cancelled(line->get_line_number()); state = DS_TERMINATED; break; case BYE: // Send 200 on the BYE request resp = r->create_response(R_200_OK); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; // Send a 487 response to terminate the pending request resp = req_in_invite->get_request()->create_response( R_487_REQUEST_TERMINATED); resp->hdr_to.set_tag(local_tag); line->send_response(resp, req_in_invite->get_tuid(), req_in_invite->get_tid()); line->call_hist_record.fail_call(resp); // Trigger call script script_in_call_failed.exec_notify(resp); MEMMAN_DELETE(resp); delete resp; ui->cb_far_end_hung_up(line->get_line_number()); state = DS_TERMINATED; break; case PRACK: // RFC 3262 3 if (respond_prack(r, tuid, tid)) { answer_call = answer_after_prack; // RFC 3262 5 // If an offer was sent in the 1xx response, then PRACK must // contain an answer if (session->sent_offer && r->body) { int warn_code; string warn_text; if (r->body->get_type() != BODY_SDP) { // Only SDP bodies are supported ui->cb_unsupported_content_type( line->get_line_number(), r); tear_down = true; } else if (session->process_sdp_answer((t_sdp *)r->body, warn_code, warn_text)) { session->recvd_answer = true; session->start_rtp(); } else { // SDP answer is not supported. // Tear down the call. ui->cb_sdp_answer_not_supported( line->get_line_number(), warn_text); tear_down = true; } } if (session->sent_offer && !r->body) { ui->cb_sdp_answer_missing(line->get_line_number()); tear_down = true; } } if (tear_down) { resp = req_in_invite->get_request()->create_response( R_400_BAD_REQUEST, "SDP answer in PRACK missing or unsupported"); resp->hdr_to.set_tag(local_tag); line->send_response(resp, req_in_invite->get_tuid(), req_in_invite->get_tid()); line->call_hist_record.fail_call(resp); // Trigger call script t_call_script script(user_config, t_call_script::TRIGGER_IN_CALL_FAILED, line->get_line_number() + 1); script.exec_notify(resp); MEMMAN_DELETE(resp); delete resp; state = DS_TERMINATED; } else if (answer_call) { answer(); } break; default: // INVITE transaction has not been completed. Deny // other requests within the dialog. resp = r->create_response(R_500_INTERNAL_SERVER_ERROR, "Session not yet established"); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; break; } } void t_dialog::state_w4answer(t_line_timer timer) { t_ip_port ip_port; t_response *resp; t_user *user_config = phone_user->get_user_profile(); t_call_script script_in_call_failed(user_config, t_call_script::TRIGGER_IN_CALL_FAILED, line->get_line_number() + 1); // RFC 3262 3 switch(timer) { case LTMR_100REL_TIMEOUT: // Retransmit 1xx response. // Send the response directly to the sender thread // bypassing the transaction layer. As this is a retransmission // from the TU, the transaction layer does not need to know. resp_1xx_invite->get_destination(ip_port); if (ip_port.ipaddr == 0) { // This should not happen. The response has been // sent before so it should be possible to sent // it again. Ignore the timeout. When the 100rel // guard timer expires, the dialog will be // cleaned up. break; } evq_sender->push_network(resp_1xx_invite, ip_port); line->start_timer(LTMR_100REL_TIMEOUT, get_object_id()); break; case LTMR_100REL_GUARD: line->stop_timer(LTMR_100REL_TIMEOUT, get_object_id()); // PRACK was not received in time. Tear down the call. resp = req_in_invite->get_request()->create_response( R_500_INTERNAL_SERVER_ERROR, "100rel timeout"); resp->hdr_to.set_tag(local_tag); line->send_response(resp, req_in_invite->get_tuid(), req_in_invite->get_tid()); line->call_hist_record.fail_call(resp); // Trigger call script script_in_call_failed.exec_notify(resp); MEMMAN_DELETE(resp); delete resp; remove_client_request(&req_in_invite); MEMMAN_DELETE(resp_1xx_invite); delete resp_1xx_invite; resp_1xx_invite = NULL; state = DS_TERMINATED; log_file->write_report("LTMR_100REL_GUARD expired.", "t_dialog::state_w4answer"); ui->cb_100rel_timeout(line->get_line_number()); break; default: // Other timeouts are not expected. Ignore. break; } } // 200 OK has been sent. Waiting for ACK void t_dialog::state_w4ack(t_request *r, t_tuid tuid, t_tid tid) { t_response *resp; t_user *user_config = phone_user->get_user_profile(); bool tear_down = false; t_client_request *cr; t_call_script script_out_call_failed(user_config, t_call_script::TRIGGER_OUT_CALL_FAILED, line->get_line_number() + 1); switch(r->method) { case ACK: // Dialog is established now. line->stop_timer(LTMR_ACK_TIMEOUT, get_object_id()); line->stop_timer(LTMR_ACK_GUARD, get_object_id()); remove_client_request(&req_in_invite); MEMMAN_DELETE(resp_invite); delete resp_invite; resp_invite = NULL; // If no offer was received in INVITE, then an offer // has been sent in 200 OK or reliable 1xx (RFC 3262). // Therefor an answer must be present in ACK if (!session->recvd_offer && r->body) { int warn_code; string warn_text; if (r->body->get_type() != BODY_SDP) { // Only SDP bodies are supported ui->cb_unsupported_content_type( line->get_line_number(), r); tear_down = true; } else if (session->process_sdp_answer((t_sdp *)r->body, warn_code, warn_text)) { session->recvd_answer = true; session->start_rtp(); } else { // SDP answer is not supported. // Tear down the call. ui->cb_sdp_answer_not_supported( line->get_line_number(), warn_text); tear_down = true; } } if (!session->recvd_offer && !r->body) { ui->cb_sdp_answer_missing(line->get_line_number()); tear_down = true; } if (end_after_ack) { ui->cb_far_end_hung_up(line->get_line_number()); state = DS_TERMINATED; } else { state = DS_CONFIRMED; if (tear_down) { send_bye(); } } ui->cb_call_established(line->get_line_number()); break; case BYE: // Send 200 on the BYE request resp = r->create_response(R_200_OK); line->send_response(resp, tuid, tid); // Trigger call script script_out_call_failed.exec_notify(resp); MEMMAN_DELETE(resp); delete resp; line->call_hist_record.end_call(true); // The session will be ended when an ACK has been // received. end_after_ack = true; break; case PRACK: // RFC 3262 3 // This is a late PRACK as the call is answered already. // Respond in a normal way to the PRACK respond_prack(r, tuid, tid); break; default: // Queue the request as ACK needs to be received first. // Note that the tuid value is not stored in the queue. // For an incoming request tuid is always 0. cr = new t_client_request(user_config, r, tid); MEMMAN_NEW(cr); inc_req_queue.push_back(cr); log_file->write_header("t_dialog::state_w4ack", LOG_NORMAL, LOG_INFO); log_file->write_raw("Waiting for ACK.\n"); log_file->write_raw("Queue incoming "); log_file->write_raw(method2str(r->method, r->unknown_method)); log_file->write_endl(); log_file->write_footer(); break; } } void t_dialog::state_w4ack_re_invite(t_request *r, t_tuid tuid, t_tid tid) { t_response *resp; t_user *user_config = phone_user->get_user_profile(); bool tear_down = false; t_call_script script_out_call_failed(user_config, t_call_script::TRIGGER_OUT_CALL_FAILED, line->get_line_number() + 1); switch(r->method) { case ACK: // re_INVITE is finished now line->stop_timer(LTMR_ACK_TIMEOUT, get_object_id()); line->stop_timer(LTMR_ACK_GUARD, get_object_id()); remove_client_request(&req_in_invite); MEMMAN_DELETE(resp_invite); delete resp_invite; resp_invite = NULL; // If no offer was received in INVITE, then an offer // has been sent in 200 OK or reliable 1xx (RFC 3262). // Therefor an answer must be present in ACK if (!session_re_invite->recvd_offer && r->body) { int warn_code; string warn_text; if (r->body->get_type() != BODY_SDP) { // Only SDP bodies are supported ui->cb_unsupported_content_type( line->get_line_number(), r); tear_down = true; } else if (session_re_invite->process_sdp_answer( (t_sdp *)r->body, warn_code, warn_text)) { session_re_invite->recvd_answer = true; } else { // SDP answer is not supported. // Tear down the call. ui->cb_sdp_answer_not_supported( line->get_line_number(), warn_text); tear_down = true; } } if (!session_re_invite->recvd_offer && !r->body) { ui->cb_sdp_answer_missing(line->get_line_number()); tear_down = true; } if (end_after_ack) { ui->cb_far_end_hung_up(line->get_line_number()); state = DS_TERMINATED; } else { state = DS_CONFIRMED; if (tear_down) { send_bye(); } else { // Make the new session description current activate_new_session(); } } break; case BYE: // Send 200 on the BYE request resp = r->create_response(R_200_OK); line->send_response(resp, tuid, tid); // Trigger call script script_out_call_failed.exec_notify(resp); MEMMAN_DELETE(resp); delete resp; line->call_hist_record.end_call(true); // The session will be ended when an ACK has been // received. end_after_ack = true; break; default: // ACK has not been received. Handle other incoming request // as if we are in the confirmed state. These incoming requests // should not change state. state_confirmed(r, tuid, tid); assert(state == DS_W4ACK_RE_INVITE); break; } } // RFC 3261 13.3.1.4 void t_dialog::state_w4ack(t_line_timer timer) { t_ip_port ip_port; // NOTE: this code is also executed for re-INVITE ACK time-outs // timeout handling for INVITE/re-INVITE is the same switch(timer) { case LTMR_ACK_TIMEOUT: // Retransmit 2xx response. // Send the response directly to the sender thread // as the INVITE transaction completed already. // (see RFC 3261 17.2.1) if (!resp_invite) break; // there is no response to send resp_invite->get_destination(ip_port); if (ip_port.ipaddr == 0) { // This should not happen. The response has been // sent before so it should be possible to sent // it again. Ignore the timeout. When the ACK // guard timer expires, the dialog will be // cleaned up. break; } evq_sender->push_network(resp_invite, ip_port); line->start_timer(LTMR_ACK_TIMEOUT, get_object_id()); break; case LTMR_ACK_GUARD: line->stop_timer(LTMR_ACK_TIMEOUT, get_object_id()); // Consider dialog as established and tear down call remove_client_request(&req_in_invite); MEMMAN_DELETE(resp_invite); delete resp_invite; resp_invite = NULL; state = DS_CONFIRMED; log_file->write_report("LTMR_ACK_GUARD expired.", "t_dialog::state_w4ack"); if (end_after_ack) { state = DS_TERMINATED; } else { send_bye(); } ui->cb_ack_timeout(line->get_line_number()); break; default: // Other timeouts are not expected. Ignore. break; } } void t_dialog::state_w4ack_re_invite(t_line_timer timer) { state_w4ack(timer); } void t_dialog::state_w4re_invite_resp(t_request *r, t_tuid tuid, t_tid tid) { t_response *resp; t_user *user_config = phone_user->get_user_profile(); t_call_script script_remote_release(user_config, t_call_script::TRIGGER_REMOTE_RELEASE, line->get_line_number() + 1); switch(r->method) { case BYE: resp = r->create_response(R_200_OK); line->send_response(resp, tuid, tid); // Trigger call script script_remote_release.exec_notify(r); MEMMAN_DELETE(resp); delete resp; ui->cb_far_end_hung_up(line->get_line_number()); line->call_hist_record.end_call(true); if (!sub_refer) { state = DS_TERMINATED; } else { state = DS_CONFIRMED_SUB; if (sub_refer->get_role() == SR_SUBSCRIBER) { // End subscription sub_refer->unsubscribe(); } } break; case ACK: // Ignore ACK break; case OPTIONS: resp = line->create_options_response(r, true); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; case PRACK: // RFC 3262 3 // This is a late PRACK. Respond in a normal way. respond_prack(r, tuid, tid); break; case SUBSCRIBE: process_subscribe(r, tuid, tid); break; case NOTIFY: process_notify(r, tuid, tid); break; default: resp = r->create_response(R_500_INTERNAL_SERVER_ERROR, "Waiting for re-INVITE response"); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; break; } } // In the confirmed state, requests will be responded. void t_dialog::state_confirmed(t_request *r, t_tuid tuid, t_tid tid) { t_response *resp; t_user *user_config = phone_user->get_user_profile(); t_call_script script_remote_release(user_config, t_call_script::TRIGGER_REMOTE_RELEASE, line->get_line_number() + 1); switch(r->method) { case INVITE: // re-INVITE process_re_invite(r, tuid, tid); break; case BYE: resp = r->create_response(R_200_OK); line->send_response(resp, tuid, tid); // Trigger call script script_remote_release.exec_notify(r); MEMMAN_DELETE(resp); delete resp; ui->cb_far_end_hung_up(line->get_line_number()); line->call_hist_record.end_call(true); if (!sub_refer) { state = DS_TERMINATED; } else { state = DS_CONFIRMED_SUB; if (sub_refer->get_role() == SR_SUBSCRIBER) { // End subscription sub_refer->unsubscribe(); } } break; case ACK: // Ignore ACK break; case OPTIONS: resp = line->create_options_response(r, true); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; case PRACK: // RFC 3262 3 // This is a late PRACK. Respond in a normal way. respond_prack(r, tuid, tid); break; case REFER: process_refer(r, tuid, tid); break; case SUBSCRIBE: process_subscribe(r, tuid, tid); break; case NOTIFY: process_notify(r, tuid, tid); break; case INFO: process_info(r, tuid, tid); break; case MESSAGE: process_message(r, tuid, tid); break; default: resp = r->create_response(R_500_INTERNAL_SERVER_ERROR); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; break; } } void t_dialog::state_confirmed(t_line_timer timer) { switch(timer) { case LTMR_GLARE_RETRY: switch(reinvite_purpose) { case REINVITE_HOLD: hold(); break; case REINVITE_RETRIEVE: retrieve(); line->retry_retrieve_succeeded(); // Note that the re-INVITE is not completed here yet. // If re-INVITE fails then line->failed_retrieve will // be called later. break; default: assert(false); } break; default: // Other timeouts are not exepcted. Ignore. break; } } void t_dialog::state_confirmed_sub(t_request *r, t_tuid tuid, t_tid tid) { t_response *resp; switch(r->method) { case OPTIONS: resp = line->create_options_response(r, true); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; case SUBSCRIBE: process_subscribe(r, tuid, tid); break; case NOTIFY: process_notify(r, tuid, tid); break; default: resp = r->create_response(R_500_INTERNAL_SERVER_ERROR); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; break; } if (!sub_refer) { // The subscription has been terminated already. state = DS_TERMINATED; } else if (sub_refer->get_state() == SS_TERMINATED) { MEMMAN_DELETE(sub_refer); delete sub_refer; sub_refer = NULL; state = DS_TERMINATED; } } void t_dialog::process_re_invite(t_request *r, t_tuid tuid, t_tid tid) { t_response *resp; t_user *user_config = phone_user->get_user_profile(); session_re_invite = session->create_clean_copy(); // Media information int warn_code; string warn_text; if (r->body) { switch(r->body->get_type()) { case BODY_SDP: if (session_re_invite-> process_sdp_offer((t_sdp*)r->body, warn_code, warn_text)) { session_re_invite->recvd_offer = true; break; } // Unsupported media resp = r->create_response( R_488_NOT_ACCEPTABLE_HERE); resp->hdr_warning.add_warning(t_warning(LOCAL_HOSTNAME, 0, warn_code, warn_text)); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; MEMMAN_DELETE(session_re_invite); delete session_re_invite; session_re_invite = NULL; // Stay in the confirmed state. The sender of the // request has to determine if the dialog needs to // be torn down by sending a BYE. return; default: // Unsupported body type. Reject call. resp = r->create_response( R_415_UNSUPPORTED_MEDIA_TYPE); // RFC 3261 21.4.13 SET_HDR_ACCEPT(resp->hdr_accept); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; MEMMAN_DELETE(session_re_invite); delete session_re_invite; session_re_invite = NULL; // Stay in the confirmed state. return; } } // If STUN is enabled, then first send a STUN binding request to // discover the IP adderss and port for media if no RTP stream // is currently active. if (phone->use_stun(user_config) && !session->is_rtp_active()) { // The STUN transaction may take a while. // Send 100 Trying resp = r->create_response(R_100_TRYING); resp->hdr_to.set_tag(""); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; if (!stun_bind_media()) { // STUN request failed. Send a 500 on the INVITE. resp = r->create_response(R_500_INTERNAL_SERVER_ERROR); resp->hdr_to.set_tag(local_tag); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; state = DS_TERMINATED; return; } } // Refresh target if (r->hdr_contact.is_populated() && r->hdr_contact.contact_list.size() > 0) { remote_target_uri = r->hdr_contact.contact_list.front().uri; remote_target_display = r-> hdr_contact.contact_list.front().display; } // Send 200 OK resp_invite = r->create_response(R_200_OK); resp_invite->hdr_to.set_tag(local_tag); // Set Contact header t_contact_param contact; contact.uri.set_url(line->create_user_contact(h_ip2str(resp_invite->get_local_ip()))); resp_invite->hdr_contact.add_contact(contact); // Set Allow and Supported headers SET_HDR_ALLOW(resp_invite->hdr_allow, user_config); SET_HDR_SUPPORTED(resp_invite->hdr_supported, user_config); // RFC 3261 13.3.1.4 // Create SDP offer if no offer was received in INVITE and no offer // was sent in a reliable 1xx response (RFC 3262 5). // Otherwise create an SDP answer. if (!session_re_invite->recvd_offer && !session_re_invite->sent_offer) { session_re_invite->create_sdp_offer(resp_invite, SDP_O_USER); } else { session_re_invite->create_sdp_answer(resp_invite, SDP_O_USER); } line->send_response(resp_invite, tuid, tid); line->start_timer(LTMR_ACK_GUARD, get_object_id()); line->start_timer(LTMR_ACK_TIMEOUT, get_object_id()); state = DS_W4ACK_RE_INVITE; } void t_dialog::process_refer(t_request *r, t_tuid tuid, t_tid tid) { t_response *resp; t_user *user_config = phone_user->get_user_profile(); t_contact_param contact; refer_accepted = true; // RFC 3515 if (sub_refer || !user_config->get_allow_refer()) { // A reference is already in progress or REFER is not // allowed. resp = r->create_response(R_603_DECLINE); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; refer_accepted = false; return; } // Check if the URI scheme is supported if (r->hdr_refer_to.uri.get_scheme() != "sip") { resp = r->create_response(R_416_UNSUPPORTED_URI_SCHEME); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; refer_accepted = false; return; } resp = r->create_response(R_202_ACCEPTED); // RFC 3515 2.2 // Contact header is mandatory contact.uri.set_url(line->create_user_contact(h_ip2str(resp->get_local_ip()))); resp->hdr_contact.add_contact(contact); if (r->hdr_refer_sub.is_populated() && !r->hdr_refer_sub.create_refer_sub) { // RFC 4488 4 resp->hdr_refer_sub.set_create_refer_sub(false); } line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; if (r->hdr_refer_sub.is_populated() && !r->hdr_refer_sub.create_refer_sub) { // RFC 4488 // The REFER-issuer requested not to create an implicit refer // subscription. log_file->write_report( "REFER-issuer requested not to create a refer subscription.", "t_dialog::process_refer"); } else { // RFC 3515 // The event header of a NOTIFY to a first REFER MAY // include the id paramter. NOTIFY's to subsequent // REFERs MUST include the id parameter (CSeq from REFER). sub_refer = new t_sub_refer(this, SR_NOTIFIER, ulong2str(r->hdr_cseq.seqnr)); MEMMAN_NEW(sub_refer); // Send immediate NOTIFY resp = new t_response(R_100_TRYING); MEMMAN_NEW(resp); if (user_config->get_ask_user_to_refer()) { // If the user has to grant permission, then the // subscription is pending. sub_refer->send_notify(resp, SUBSTATE_PENDING); } else { sub_refer->send_notify(resp, SUBSTATE_ACTIVE); } MEMMAN_DELETE(resp); delete resp; } // Ask permission to refer if (user_config->get_ask_user_to_refer()) { if (r->hdr_referred_by.is_populated()) { ui->cb_ask_user_to_refer(user_config, r->hdr_refer_to.uri, r->hdr_refer_to.display, r->hdr_referred_by.uri, r->hdr_referred_by.display); } else { ui->cb_ask_user_to_refer(user_config, r->hdr_refer_to.uri, r->hdr_refer_to.display, t_url(), ""); } } else { ui->send_refer_permission(true); } // NOTE: refer_accepted = true, though the answer to permission // is not given yet. So this means, that the refer is not // rejected at this moment. It may be rejected by the user. } void t_dialog::recvd_refer_permission(bool permission, t_request *r) { t_response *resp; // NOTE: if the REFER-issuer requested not to create a refer // subscription (RFC 4488), then no NOTIFY can be sent to signal // the rejection. if (!permission && sub_refer) { // User denied REFER // RFC 3515 2.4.5 resp = new t_response(R_603_DECLINE); MEMMAN_NEW(resp); sub_refer->send_notify(resp, SUBSTATE_TERMINATED, EV_REASON_REJECTED); MEMMAN_DELETE(resp); delete resp; } refer_accepted = permission; } void t_dialog::process_subscribe(t_request *r, t_tuid tuid, t_tid tid) { t_response *resp; if (sub_refer && sub_refer->match(r)) { sub_refer->recv_subscribe(r, tuid, tid); if (sub_refer->get_state() == SS_TERMINATED) { MEMMAN_DELETE(sub_refer); delete sub_refer; sub_refer = NULL; } return; } resp = r->create_response(R_481_TRANSACTION_NOT_EXIST, REASON_481_SUBSCRIPTION_NOT_EXIST); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; } void t_dialog::process_notify(t_request *r, t_tuid tuid, t_tid tid) { t_response *resp; if (!sub_refer && (refer_state == REFST_W4RESP || refer_state == REFST_W4NOTIFY)) { // First NOTIFY after sending a REFER sub_refer = new t_sub_refer(this, SR_SUBSCRIBER, r->hdr_event.id); MEMMAN_NEW(sub_refer); refer_state = REFST_PENDING; } if (sub_refer && sub_refer->match(r)) { sub_refer->recv_notify(r, tuid, tid); if (sub_refer->get_state() == SS_TERMINATED) { // Set the refer state to NULL before calling the UI // call back functions as the user interface might use // the refer state to render the correct status to the // user. refer_state = REFST_NULL; // Determine outcome of the reference switch(sub_refer->get_sr_result()) { case SRR_INPROG: // The outcome of the reference is unknown. // Treat it as a success as no new info will // come to the referrer. refer_succeeded = true; ui->cb_refer_result_inprog(line->get_line_number()); break; case SRR_FAILED: refer_succeeded = false; ui->cb_refer_result_failed(line->get_line_number()); break; case SRR_SUCCEEDED: refer_succeeded = true; ui->cb_refer_result_success(line->get_line_number()); break; default: assert(false); } MEMMAN_DELETE(sub_refer); delete sub_refer; sub_refer = NULL; } else if (!sub_refer->is_pending()) { refer_state = REFST_ACTIVE; } return; } // RFC 3265 3.2.4 resp = r->create_response(R_481_TRANSACTION_NOT_EXIST, REASON_481_SUBSCRIPTION_NOT_EXIST); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; } void t_dialog::process_info(t_request *r, t_tuid tuid, t_tid tid) { t_response *resp; // RFC 2976 2.2 // A 200 OK response MUST be sent by a UAS for an INFO request with // no message body if the INFO request was successfully received for // an existing call. if (!r->body) { resp = r->create_response(R_200_OK); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; return; } if (r->body->get_type() != BODY_DTMF_RELAY) { resp = r->create_response(R_415_UNSUPPORTED_MEDIA_TYPE); resp->hdr_accept.add_media(t_media("application", "dtmf-relay")); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; return; } char dtmf_signal = ((t_sip_body_dtmf_relay *)r->body)->signal; if (!is_valid_dtmf_sym(dtmf_signal)) { resp = r->create_response(R_400_BAD_REQUEST, "Invalid DTMF signal"); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; return; } resp = r->create_response(R_200_OK); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; ui->cb_dtmf_detected(line->get_line_number(), char2dtmf_ev(dtmf_signal)); } void t_dialog::process_message(t_request *r, t_tuid tuid, t_tid tid) { t_response *resp; t_user *user_config = phone_user->get_user_profile(); log_file->write_report("Received in-dialog MESSAGE.", "t_dialog::process_message", LOG_NORMAL, LOG_DEBUG); if (!r->body || !MESSAGE_CONTENT_TYPE_SUPPORTED(*r)) { resp = r->create_response(R_415_UNSUPPORTED_MEDIA_TYPE); // RFC 3261 21.4.13 SET_MESSAGE_HDR_ACCEPT(resp->hdr_accept); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; return; } if (r->body && r->body->get_type() == BODY_IM_ISCOMPOSING_XML) { // Message composing indication t_im_iscomposing_xml_body *sb = dynamic_cast(r->body); im::t_composing_state state = im::string2composing_state(sb->get_state()); time_t refresh = sb->get_refresh(); ui->cb_im_iscomposing_request(line->get_user(), r, state, refresh); resp = r->create_response(R_200_OK); } else { // Instant message bool accepted = ui->cb_message_request(line->get_user(), r); if (accepted) { resp = r->create_response(R_200_OK); } else { if (user_config->get_im_max_sessions() == 0) { resp = r->create_response(R_603_DECLINE); } else { resp = r->create_response(R_486_BUSY_HERE); } } } line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; } // INVITE sent. Waiting for a first non-100 response. void t_dialog::state_w4invite_resp(t_response *r, t_tuid tuid, t_tid tid) { if (r->hdr_cseq.method != INVITE) return; t_user *user_config = phone_user->get_user_profile(); // 1XX (except 100) and 2XX establish the dialog. // Update the state for dialog establishment. // RFC 3261 12.1.2 switch (r->get_class()) { case R_1XX: if (r->code == R_100_TRYING) break; // RFC 3262 4 // Discard retransmissions and out-of-sequence reliable // provisional responses. if (must_discard_100rel(r)) return; // fall thru case R_2XX: // Set remote tag remote_tag = r->hdr_to.tag; create_route_set(r); create_remote_target(r); // Set remote URI and display name remote_uri = r->hdr_to.uri; remote_display = r->hdr_to.display; process_1xx_2xx_invite_resp(r); break; default: break; } // RFC 3262 // Send PRACK if required send_prack_if_required(r); t_call_script script_out_call_answered(user_config, t_call_script::TRIGGER_OUT_CALL_ANSWERED, line->get_line_number() + 1); t_call_script script_out_call_failed(user_config, t_call_script::TRIGGER_OUT_CALL_FAILED, line->get_line_number() + 1); switch (r->get_class()) { case R_1XX: // Provisional response received. line->ci_set_last_provisional_reason(r->reason); ui->cb_provisional_resp_invite(line->get_line_number(), r); if (r->code > R_100_TRYING && r->hdr_to.tag.size() > 0) { state = DS_EARLY; } else { state = DS_W4INVITE_RESP2; } // User indicated that the request should be cancelled. // Now that the first provisional response has been received, // a CANCEL can be sent. if (request_cancelled) { send_cancel(true); } break; case R_2XX: // Stop cancel guard timer if it was running line->stop_timer(LTMR_CANCEL_GUARD, get_object_id()); // Success received. ack_2xx_invite(r); // Check for REFER support // If the Allow header is not present then assume REFER // is supported. if (!r->hdr_allow.is_populated() || r->hdr_allow.contains_method(REFER)) { line->ci_set_refer_supported(true); } // Trigger call script script_out_call_answered.exec_notify(r); ui->cb_call_answered(user_config, line->get_line_number(), r); line->call_hist_record.answer_call(r); state = DS_CONFIRMED; if (request_cancelled) { // User indicated that the request should be cancelled, // but no response was received yet. A final response // has been received. Instead of CANCEL a BYE will be // sent now. send_bye(); } else if (end_after_2xx_invite) { // Or user cancelled the request already, but the 2XX // glared with CANCEL. log_file->write_report("CANCEL / 2XX INVITE glare.", "t_dialog::state_w4invite_resp"); send_bye(); } break; case R_3XX: case R_4XX: case R_5XX: case R_6XX: default: // Stop cancel guard timer if it was running line->stop_timer(LTMR_CANCEL_GUARD, get_object_id()); // Final response (failure) received. // Treat unknown response classes as failure. // Trigger call script script_out_call_failed.exec_notify(r); ui->cb_stop_call_notification(line->get_line_number()); ui->cb_call_failed(user_config, line->get_line_number(), r); line->call_hist_record.fail_call(r); remove_client_request(&req_out_invite); state = DS_TERMINATED; break; } // Notify progress to the referror if this is a referred call if (is_referred_call) { get_phone()->notify_refer_progress(r, line->get_line_number()); } } void t_dialog::state_w4invite_resp(t_line_timer timer) { switch (timer) { case LTMR_CANCEL_GUARD: log_file->write_report("Timer LTMR_CANCEL_GUARD expired.", "t_dialog::state_w4invite_resp", LOG_NORMAL, LOG_WARNING); // CANCEL has been responded to, but 487 on INVITE was never // received. Abort the INVITE transaction. if (req_out_invite) { t_tid _tid = req_out_invite->get_tid(); if (_tid > 0) { evq_trans_mgr->push_abort_trans(_tid); } } break; default: // Ignore other timeouts break; } } // INVITE response sent. At least 1 provisional response (not 100 Trying) // received. void t_dialog::state_early(t_response *r, t_tuid tuid, t_tid tid) { if (r->hdr_cseq.method != INVITE) return; t_user *user_config = phone_user->get_user_profile(); switch (r->get_class()) { case R_1XX: // RFC 3262 4 // Discard retransmissiona and out-of-sequence reliable // provisional responses. if (must_discard_100rel(r)) return; // fall thru case R_2XX: create_route_set(r); create_remote_target(r); process_1xx_2xx_invite_resp(r); break; default: break; } // RFC 3262 // Send PRACK if required send_prack_if_required(r); t_call_script script_out_call_answered(user_config, t_call_script::TRIGGER_OUT_CALL_ANSWERED, line->get_line_number() + 1); t_call_script script_out_call_failed(user_config, t_call_script::TRIGGER_OUT_CALL_FAILED, line->get_line_number() + 1); switch (r->get_class()) { case R_1XX: // Provisional response received. line->ci_set_last_provisional_reason(r->reason); ui->cb_provisional_resp_invite(line->get_line_number(), r); if (request_cancelled) { send_cancel(true); } break; case R_2XX: // Stop cancel guard timer if it was running line->stop_timer(LTMR_CANCEL_GUARD, get_object_id()); // Success received. ack_2xx_invite(r); // Check for REFER support // If the Allow header is not present then assume REFER // is supported. if (!r->hdr_allow.is_populated() || r->hdr_allow.contains_method(REFER)) { line->ci_set_refer_supported(true); } // Trigger call script script_out_call_answered.exec_notify(r); ui->cb_call_answered(user_config, line->get_line_number(), r); line->call_hist_record.answer_call(r); state = DS_CONFIRMED; if (request_cancelled) { // User indicated that the request should be cancelled, // but no response was received yet. A final response // has been received. Instead of CANCEL a BYE will be // sent now. send_bye(); } else if (end_after_2xx_invite) { // Or user cancelled the request already, but the 2XX // glared with CANCEL. log_file->write_report("CANCEL / 2XX INVITE glare.", "t_dialog::state_w4invite_resp"); send_bye(); } break; case R_3XX: case R_4XX: case R_5XX: case R_6XX: default: // Stop cancel guard timer if it was running line->stop_timer(LTMR_CANCEL_GUARD, get_object_id()); // Final response (failure) received. // Treat unknown response classes as failure. // Trigger call script script_out_call_failed.exec_notify(r); ui->cb_stop_call_notification(line->get_line_number()); ui->cb_call_failed(user_config, line->get_line_number(), r); line->call_hist_record.fail_call(r); remove_client_request(&req_out_invite); state = DS_TERMINATED; break; } // Notify progress to the referror if this is a referred call if (is_referred_call) { get_phone()->notify_refer_progress(r, line->get_line_number()); } } void t_dialog::state_early(t_line_timer timer) { switch (timer) { case LTMR_CANCEL_GUARD: log_file->write_report("Timer LTMR_CANCEL_GUARD expired.", "t_dialog::state_early", LOG_NORMAL, LOG_WARNING); // CANCEL has been responded to, but 487 on INVITE was never // received. Abort the INVITE transaction. if (req_out_invite) { t_tid _tid = req_out_invite->get_tid(); if (_tid > 0) { evq_trans_mgr->push_abort_trans(_tid); } } break; default: // Ignore other timeouts break; } } // BYE sent. Waiting for response. void t_dialog::state_w4bye_resp(t_response *r, t_tuid tuid, t_tid tid) { if (r->hdr_cseq.method != BYE) return; switch (r->get_class()) { case R_1XX: // Provisional response received. Wait for final response. break; default: // All final responses terminate the dialog. remove_client_request(&req_out); if (!sub_refer) { state = DS_TERMINATED; } else { state = DS_CONFIRMED_SUB; if (sub_refer->get_role() == SR_SUBSCRIBER) { // End subscription sub_refer->unsubscribe(); } } break; } } void t_dialog::state_w4bye_resp(t_request *r, t_tuid tuid, t_tid tid) { t_response *resp; switch(r->method) { case BYE: resp = r->create_response(R_200_OK); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; // A BYE glare situation. Keep waiting for the BYE // response. break; default: resp = r->create_response(R_500_INTERNAL_SERVER_ERROR); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; break; } } // Confirmed dialog. Responses are for mid-dialog requests. void t_dialog::state_confirmed_resp(t_response *r, t_tuid tuid, t_tid tid) { // 1XX responses are not expected. If they are received // then simply ignore them. if (r->is_provisional()) return; switch (r->hdr_cseq.method) { case OPTIONS: ui->cb_options_response(r); remove_client_request(&req_out); break; case REFER: remove_client_request(&req_refer); if (refer_state != REFST_W4RESP) { // NOTIFY has already been received. No need to // process the REFER response anymore. Interesting // issue might be: what if NOTIFY has been received and // now a failure response comes in? break; } if (!r->is_success()) { // REFER failed refer_state = REFST_NULL; refer_succeeded = false; // KLUDGE: only signal REFER failure in case of // non-408/481 responses. These responses // clear the line, so the upper layers should not // take action on the failed refer. if (r->code != R_408_REQUEST_TIMEOUT || r->code == R_481_TRANSACTION_NOT_EXIST) { out_refer_req_failed = true; } ui->cb_refer_failed(line->get_line_number(), r); break; } refer_state = REFST_W4NOTIFY; break; case INFO: remove_client_request(&req_info); if (!dtmf_queue.empty()) { char digit = dtmf_queue.front(); dtmf_queue.pop(); send_dtmf(digit, false, true); } break; default: // The received response should match the pending request. // So this point should never be reached. assert(false); break; } // RFC 3261 12.2.1.2 // If a mid-dialog request is timed out, or the call/transaction // does not exist anymore at the server, then terminate the // dialog. if (r->code == R_408_REQUEST_TIMEOUT || r->code == R_481_TRANSACTION_NOT_EXIST) { send_bye(); } } void t_dialog::state_w4re_invite_resp(t_response *r, t_tuid tuid, t_tid tid) { if (r->hdr_cseq.method != INVITE) return; switch (r->get_class()) { case R_1XX: if (r->code == R_100_TRYING) break; // RFC 3262 4 // Discard retransmissiona and out-of-sequence reliable // provisional responses. if (must_discard_100rel(r)) return; if (state == DS_W4RE_INVITE_RESP2) { // RFC 3262 // Discard retransmissions and out-of-order // reliable provisional responses. if (must_discard_100rel(r)) return; } // RFC 3262 // Send PRACK if required send_prack_if_required(r); // fall thru case R_2XX: // Process SDP answer if answer is present and no // answer has been received yet. if (!session_re_invite->recvd_answer && r->body) { int warn_code; string warn_text; if (r->body->get_type() != BODY_SDP) { // Only SDP bodies are supported ui->cb_unsupported_content_type( line->get_line_number(), r); request_cancelled = true; } else if (session_re_invite-> process_sdp_answer((t_sdp *)r->body, warn_code, warn_text)) { session_re_invite->recvd_answer = true; } else { // SDP answer is not supported. Cancel // the INVITE. request_cancelled = true; ui->cb_sdp_answer_not_supported( line->get_line_number(), warn_text); break; } } // This implementation always sends an offer in // INVITE. So an answer must be in a 2XX response // as PRACK is not supported. if (r->get_class() == R_2XX && !r->body) { request_cancelled = true; ui->cb_sdp_answer_missing(line->get_line_number()); break; } // Refresh target URI and display name if (r->get_class() == R_2XX && r->hdr_contact.is_populated() && r->hdr_contact.contact_list.size() > 0) { remote_target_uri = r-> hdr_contact.contact_list.front().uri; remote_target_display = r-> hdr_contact.contact_list.front().display; } break; default: break; } switch (r->get_class()) { case R_1XX: // Provisional response received. state = DS_W4RE_INVITE_RESP2; // Start re-INVITE guard timer (no RFC requirement) line->start_timer(LTMR_RE_INVITE_GUARD, get_object_id()); // User indicated that the request should be cancelled. // Now that the first provional response has been received, // a CANCEL can be sent. if (request_cancelled) { send_cancel(true); } break; case R_2XX: // Success received. line->stop_timer(LTMR_RE_INVITE_GUARD, get_object_id()); ack_2xx_invite(r); ui->cb_reinvite_success(line->get_line_number(), r); state = DS_CONFIRMED; if (request_cancelled) { // User indicated that the request should be cancelled, // but no response was received yet. A final response // has been received. Instead of CANCEL a BYE will be // sent now. send_bye(); } else if (end_after_2xx_invite) { // Or user cancelled the request already, but the 2XX // glared with CANCEL. log_file->write_report("CANCEL / 2XX INVITE glare.", "t_dialog::state_w4invite_resp"); send_bye(); } else { // Make the re-INIVTE session info the current info activate_new_session(); } break; case R_3XX: case R_4XX: case R_5XX: case R_6XX: default: // Final response (failure) received. // Treat unknown response classes as failure. line->stop_timer(LTMR_RE_INVITE_GUARD, get_object_id()); ui->cb_reinvite_failed(line->get_line_number(), r); remove_client_request(&req_out_invite); // RFC 3261 14.1 // delete re-INVITE session info. Old session info // stays as re-INVITE failed. MEMMAN_DELETE(session_re_invite); delete session_re_invite; session_re_invite = NULL; state = DS_CONFIRMED; switch(reinvite_purpose) { case REINVITE_HOLD: // A call hold may not fail for the user as // this cause problems with soundcard access and // showing line status in the GUI. Even though re-INVITE // failed, the RTP still stopped. So simply indicated // that the hold failed, such that a subsequent retrieve // can simply restart the RTP. hold_failed = true; break; case REINVITE_RETRIEVE: line->failed_retrieve(); if (r->code != R_491_REQUEST_PENDING) { ui->cb_retrieve_failed(line->get_line_number(), r); } break; default: assert(false); } // RFC 3261 14.1 // Start wait timer before retrying a re-INVITE after a // glare. if (r->code == R_491_REQUEST_PENDING) { line->start_timer(LTMR_GLARE_RETRY, get_object_id()); } // RFC 3261 14.1 if (r->code == R_408_REQUEST_TIMEOUT || r->code == R_481_TRANSACTION_NOT_EXIST) { send_bye(); } break; } } void t_dialog::state_w4re_invite_resp(t_line_timer timer) { switch(timer) { case LTMR_RE_INVITE_GUARD: // Abort the INVITE as the user cannot terminate // it in a normal way. if (req_out_invite) { t_tid _tid = req_out_invite->get_tid(); if (_tid > 0) { evq_trans_mgr->push_abort_trans(_tid); } } else { // Consider this as if a 408 Timeout response has // been received. Terminate the dialog. send_bye(); } break; default: break; } } void t_dialog::activate_new_session(void) { if (session->equal_audio(*session_re_invite)) { log_file->write_report("SDP in re-INVITE is a noop.", "t_dialog::activate_new_session"); MEMMAN_DELETE(session_re_invite); delete session_re_invite; session_re_invite = NULL; return; } log_file->write_report("Renew session as specified by SDP in re-INVITE.", "t_dialog::activate_new_session"); // Stop current session MEMMAN_DELETE(session); delete session; // Create new session session = session_re_invite; session_re_invite = NULL; session->start_rtp(); } void t_dialog::process_1xx_2xx_invite_resp(t_response *r) { t_user *user_config = phone_user->get_user_profile(); // Process SDP answer if answer is present and no // answer has been received yet. if (r->body) { int warn_code; string warn_text; if (r->body->get_type() != BODY_SDP) { // Only SDP bodies are supported ui->cb_unsupported_content_type(line->get_line_number(), r); request_cancelled = true; } else if (!session->recvd_answer || (user_config->get_allow_sdp_change() && ((t_sdp *)r->body)->origin.session_version != session->dst_sdp_version)) { // Only process SDP if no SDP was received yet (RFC 3261 // 13.3.1. Or process SDP if overridden by the // allow_sdp_change setting in the user profile. // A changed SDP must have a new version number (RFC 3264) if (session->process_sdp_answer((t_sdp *)r->body, warn_code, warn_text)) { // If this is a changed SDP, then stop the // current RTP stream based on the previous SDP. if (session->recvd_answer) session->stop_rtp(); session->recvd_answer = true; // The following code part handles the ugly interaction // between forking and early media (Vonage uses this). // In case of forking 1xx responses with SDP may com // from different destinations. Only the first 1xx will // create a media stream. Media streams on other legs cannot // be created as that would give sound conflicts. // When a 2xx response with SDP is received, an early media // stream on another leg must be killed. // Due to forking multiple 2xx repsonses from different // destinations may be received. Only the first 2xx response // will create a media session. The other dialogs receiving // a 2xx will be released immediately anyway (see line.cpp). bool start_media = true; t_dialog *d = line->get_dialog_with_active_session(); if (d != NULL) { if (r->get_class() == R_2XX && d->get_state() != DS_CONFIRMED) { log_file->write_header( "t_dialog::process_1xx_2xx_invite_resp"); log_file->write_raw( "Kill early media on another dialog, id="); log_file->write_raw(d->get_object_id()); log_file->write_endl(); log_file->write_footer(); d->kill_rtp(); } else { log_file->write_header( "t_dialog::process_1xx_2xx_invite_resp"); log_file->write_raw( "Cannot start media as another dialog (id="); log_file->write_raw(d->get_object_id()); log_file->write_raw(") already has media.\n"); log_file->write_footer(); start_media = false; } } if (start_media) { if (r->is_provisional()) { log_file->write_report("Starting early media.", "t_dialog::process_1xx_2xx_invite_resp"); } // Stop locally played tones to free the soundcard // for the voice stream ui->cb_stop_call_notification(line->get_line_number()); session->start_rtp(); } } else { // SDP answer is not supported. Cancel // the INVITE. request_cancelled = true; ui->cb_sdp_answer_not_supported( line->get_line_number(), warn_text); } } } else if (r->code == R_180_RINGING && !ringing_received && !session->recvd_answer) { // There is no SDP and far-end indicated that it is ringing // so generate ring back tone locally. ui->cb_play_ringback(user_config); ringing_received = true; } // This implementation always sends an offer in // INVITE. So an answer must be in a 2XX response if // no answer has been received in a provisional response. if (!session->recvd_answer && r->get_class() == R_2XX && !r->body) { request_cancelled = true; ui->cb_sdp_answer_missing(line->get_line_number()); } // RFC 3261 13.3.1.4 // A 2XX response to an INVITE should contain a Supported header // listing all supported extensions. // Set extensions supported by remote party if (r->get_class() == R_2XX && r->hdr_supported.is_populated()) { remote_extensions.insert(r->hdr_supported.features.begin(), r->hdr_supported.features.end()); } } void t_dialog::ack_2xx_invite(t_response *r) { t_ip_port ip_port; t_user *user_config = phone_user->get_user_profile(); if (ack) { // delete previous cached ACK MEMMAN_DELETE(ack); delete ack; } ack = create_request(ACK); ack->get_destination(ip_port, *user_config); // If for some strange reason the destination could // not be computed then wait for a retransmission of // 2XX. if (ip_port.ipaddr != 0 && ip_port.port != 0) { evq_sender->push_network(ack, ip_port); } else { log_file->write_header("t_dialog::ack_2xx_invite", LOG_SIP, LOG_CRITICAL); log_file->write_raw("Cannot determine destination IP address for ACK.\n\n"); log_file->write_raw(ack->encode()); log_file->write_footer(); } remove_client_request(&req_out_invite); } void t_dialog::send_prack_if_required(t_response *r) { t_user *user_config = phone_user->get_user_profile(); // RFC 3262 // Send PRACK if needed if (r->get_class() == R_1XX && r->code != R_100_TRYING) { // RFC 3262 4 // Send PRACK if the 1xx response is sent reliable and 100rel // is enabled. if (r->hdr_to.tag.size() > 0 && r->hdr_require.contains(EXT_100REL) && r->hdr_rseq.is_populated() && remote_target_uri.is_valid() && user_config->get_ext_100rel() != EXT_DISABLED) { t_request *prack = create_request(PRACK); prack->hdr_rack.set_method(r->hdr_cseq.method); prack->hdr_rack.set_cseq_nr(r->hdr_cseq.seqnr); prack->hdr_rack.set_resp_nr(r->hdr_rseq.resp_nr); // Delete previous PRACK request if it is still pending if (req_prack) { log_file->write_report("Previous PRACK still pending.", "t_dialog::send_prack_if_needed"); remove_client_request(&req_prack); } req_prack = new t_client_request(user_config, prack, 0); MEMMAN_NEW(req_prack); line->send_request(prack, req_prack->get_tuid()); MEMMAN_DELETE(prack); delete prack; } } } bool t_dialog::must_discard_100rel(t_response *r) { t_user *user_config = phone_user->get_user_profile(); // RFC 3262 4 // Discard retransmissiona and out-of-sequence reliable // provisional responses. if (r->code > R_100_TRYING && r->hdr_to.tag.size() > 0 && r->hdr_require.contains(EXT_100REL) && r->hdr_rseq.is_populated() && user_config->get_ext_100rel() != EXT_DISABLED) { if (remote_resp_nr == 0) { // This is the first response with a repsonse nr. // Initialize the remote response nr remote_resp_nr = r->hdr_rseq.resp_nr; return false; } if (r->hdr_rseq.resp_nr <= remote_resp_nr) { // This is a retransmission. // PRACK has already been sent. The transaction // layer takes care of retransmitting PRACK // if PRACK got lost. log_file->write_report("Discard 1xx retransmission.", "t_dialog::must_discard_100rel"); return true; } if (r->hdr_rseq.resp_nr != remote_resp_nr + 1) { // A provisional response has been lost. // Discard this response and wait for a retransmission // of the lost response. log_file->write_report("Discard out-of-order 1xx", "t_dialog::must_discard_100rel"); return true; } } remote_resp_nr = r->hdr_rseq.resp_nr; return false; } bool t_dialog::respond_prack(t_request *r, t_tuid tuid, t_tid tid) { t_response *resp; // RFC 3262 3 if (resp_1xx_invite && r->hdr_rack.method == resp_1xx_invite->hdr_cseq.method && r->hdr_rack.cseq_nr == resp_1xx_invite->hdr_cseq.seqnr && r->hdr_rack.resp_nr == resp_1xx_invite->hdr_rseq.resp_nr) { // The provisional response has been delivered now. line->stop_timer(LTMR_100REL_TIMEOUT, get_object_id()); line->stop_timer(LTMR_100REL_GUARD, get_object_id()); MEMMAN_DELETE(resp_1xx_invite); delete resp_1xx_invite; resp_1xx_invite = NULL; // Send 200 on the PRACK request resp = r->create_response(R_200_OK); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; return true; } else { // PRACK does not match pending 1xx response // Send a 481 on the PRACK request resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; return false; } } void t_dialog::send_request(t_request *r, t_tuid tuid) { line->send_request(r, tuid); } //////////// // Public //////////// t_dialog::t_dialog(t_line *_line) : t_abstract_dialog(_line->get_phone_user()) { line = _line; req_out = NULL; req_out_invite = NULL; req_in_invite = NULL; req_cancel = NULL; req_prack = NULL; req_refer = NULL; req_info = NULL; req_stun = NULL; request_cancelled = false; end_after_ack = false; end_after_2xx_invite = false; answer_after_prack = false; ringing_received = false; resp_invite = NULL; resp_1xx_invite = NULL; ack = NULL; state = DS_NULL; // Timers dur_ack_timeout = 0; id_ack_timeout = 0; id_ack_guard = 0; id_re_invite_guard = 0; id_glare_retry = 0; id_cancel_guard = 0; // RFC 3262 // Timers dur_100rel_timeout = 0; id_100rel_timeout = 0; id_100rel_guard = 0; t_user *user_config = phone_user->get_user_profile(); // Create session session = new t_session(this, USER_HOST(user_config, AUTO_IP4_ADDRESS), line->get_rtp_port()); MEMMAN_NEW(session); session_re_invite = NULL; // Subscription sub_refer = NULL; is_referred_call = false; refer_state = REFST_NULL; refer_accepted = false; refer_succeeded = false; out_refer_req_failed = false; } t_dialog::~t_dialog() { if (req_out) remove_client_request(&req_out); if (req_out_invite) remove_client_request(&req_out_invite); if (req_in_invite) remove_client_request(&req_in_invite); if (req_cancel) remove_client_request(&req_cancel); if (req_prack) remove_client_request(&req_prack); if (req_refer) remove_client_request(&req_refer); if (req_info) remove_client_request(&req_info); if (req_stun) remove_client_request(&req_stun); if (resp_invite) { MEMMAN_DELETE(resp_invite); delete resp_invite; } if (resp_1xx_invite) { MEMMAN_DELETE(resp_1xx_invite); delete resp_1xx_invite; } if (ack) { MEMMAN_DELETE(ack); delete ack; } if (session) { MEMMAN_DELETE(session); delete session; } if (session_re_invite) { MEMMAN_DELETE(session_re_invite); delete session_re_invite; } if (sub_refer) { MEMMAN_DELETE(sub_refer); delete sub_refer; } for (list::iterator i = inc_req_queue.begin(); i != inc_req_queue.end(); i++) { MEMMAN_DELETE(*i); delete *i; } } // Copy will only be used on the open dialog. t_dialog *t_dialog::copy(void) { t_dialog *d = new t_dialog(*this); MEMMAN_NEW(d); d->generate_new_id(); // Increment reference count on client request if (req_out) d->req_out->inc_ref_count(); if (req_out_invite) d->req_out_invite->inc_ref_count(); if (req_in_invite) d->req_in_invite->inc_ref_count(); if (req_prack) d->req_prack->inc_ref_count(); if (req_refer) d->req_refer->inc_ref_count(); if (req_stun) d->req_stun->inc_ref_count(); // The open dialog will handle the CANCEL, so delete it // from the copy. if (req_cancel) d->req_cancel = NULL; if (resp_invite) d->resp_invite = (t_response *)resp_invite->copy(); if (resp_1xx_invite) d->resp_1xx_invite = (t_response *)resp_1xx_invite->copy(); if (ack) d->ack = (t_request *)ack->copy(); dur_ack_timeout = 0; id_ack_timeout = 0; id_ack_guard = 0; dur_100rel_timeout = 0; id_100rel_timeout = 0; id_100rel_guard = 0; if (session) { d->session = new t_session(*session); MEMMAN_NEW(d->session); d->session->set_owner(d); // If an audio session was already created for early media // then the audio session will be moved to the copy of the // dialog. Only 1 dialog can have an audio session. // See process_1xx_2xx_invite_resp for more information on // early media problems. // Clear a possible audio session in the open dialog. t_audio_session *as = session->get_audio_session(); if (as) { as->set_session(d->session); session->set_audio_session(NULL); log_file->write_report( "An audio session was created on an open dialog.", "t_dialog::copy", LOG_NORMAL, LOG_DEBUG); } } log_file->write_header("t_dialog::copy", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Created dialog through copy, id="); log_file->write_raw(d->get_object_id()); log_file->write_endl(); log_file->write_footer(); return d; } void t_dialog::send_invite(const t_url &to_uri, const string &to_display, const string &subject, const t_hdr_referred_by &hdr_referred_by, const t_hdr_replaces &hdr_replaces, const t_hdr_require &hdr_require, const t_hdr_request_disposition &hdr_request_disposition, bool anonymous) { t_user *user_config = phone_user->get_user_profile(); if (state != DS_NULL) { throw X_DIALOG_ALREADY_ESTABLISHED; } // If STUN is enabled, then first send a STUN binding request to // discover the IP adderss and port for media. if (phone->use_stun(user_config)) { if (!stun_bind_media()) { ui->cb_stun_failed_call_ended(line->get_line_number()); state = DS_TERMINATED; return; } } t_request invite(INVITE); // RFC 3261 12.2.1.1 // Request URI and Route header invite.set_route(to_uri, phone_user->get_service_route()); // Set Call-ID header call_id = NEW_CALL_ID(user_config); invite.hdr_call_id.set_call_id(call_id); call_id_owner = true; // Set To header invite.hdr_to.set_uri(to_uri); invite.hdr_to.set_display(to_display); // Set From header local_tag = NEW_TAG; local_uri.set_url(line->create_user_uri()); local_display = user_config->get_display(anonymous); invite.hdr_from.set_uri(local_uri); invite.hdr_from.set_display(local_display); invite.hdr_from.set_tag(local_tag); // Privacy header if (line->get_hide_user()) { invite.hdr_privacy.add_privacy(PRIVACY_ID); } // Set P-Preferred-Identity header if (anonymous && user_config->get_send_p_preferred_id()) { t_identity identity; identity.set_uri(user_config->create_user_uri(false)); identity.set_display(user_config->get_display(false)); invite.hdr_p_preferred_identity.add_identity(identity); } // Set CSeq header local_seqnr = rand() % 1000 + 1; invite.hdr_cseq.set_method(INVITE); invite.hdr_cseq.set_seqnr(local_seqnr); // Set Max-Forwards header invite.hdr_max_forwards.set_max_forwards(MAX_FORWARDS); // User-Agent SET_HDR_USER_AGENT(invite.hdr_user_agent); // RFC 3261 13.2.1 // Allow and Supported headers SET_HDR_ALLOW(invite.hdr_allow, user_config); SET_HDR_SUPPORTED(invite.hdr_supported, user_config); // Extensions specific for INVITE if (user_config->get_ext_100rel() != EXT_DISABLED) { invite.hdr_supported.add_feature(EXT_100REL); } // Require header switch (user_config->get_ext_100rel()) { case EXT_PREFERRED: case EXT_REQUIRED: invite.hdr_require.add_feature(EXT_100REL); break; default: break; } // Subject header if (subject != "") { invite.hdr_subject.set_subject(subject); } // Organization if (!anonymous) { SET_HDR_ORGANIZATION(invite.hdr_organization, user_config); } // RFC 3892 Referred-By header if a call is initated because // of an incoming REFER. invite.hdr_referred_by = hdr_referred_by; // RFC 3891 Replaces header invite.hdr_replaces = hdr_replaces; // Add required extension passed by the upper layer if (hdr_require.is_populated()) { invite.hdr_require.add_features(hdr_require.features); } // RFC 3841 Request-Disposition header invite.hdr_request_disposition = hdr_request_disposition; // Calculate destinations // See create_request() for more comments invite.calc_destinations(*user_config); // The Contatc, Via header and SDP can only be created after the destinations // are calculated, because the destination deterimines which // local IP address should be used. // Create SDP offer session->create_sdp_offer(&invite, SDP_O_USER); // Set Via header unsigned long local_ip = invite.get_local_ip(); t_via via(USER_HOST(user_config, h_ip2str(local_ip)), PUBLIC_SIP_PORT(user_config)); invite.hdr_via.add_via(via); // Set Contact header t_contact_param contact; contact.uri.set_url(line->create_user_contact(h_ip2str(local_ip))); invite.hdr_contact.add_contact(contact); // Send INVITE req_out_invite = new t_client_request(user_config, &invite, 0); MEMMAN_NEW(req_out_invite); // Trigger call script t_call_script script(user_config, t_call_script::TRIGGER_OUT_CALL, line->get_line_number() + 1); script.exec_notify(&invite); line->send_request(&invite, req_out_invite->get_tuid()); line->call_hist_record.start_call(&invite, t_call_record::DIR_OUT, user_config->get_profile_name()); state = DS_W4INVITE_RESP; } bool t_dialog::resend_invite_auth(t_response *resp) { t_user *user_config = phone_user->get_user_profile(); if (!req_out_invite) return false; assert(state == DS_W4INVITE_RESP || state == DS_W4INVITE_RESP2); t_request *req = req_out_invite->get_request(); // Add authorization header, increment CSeq and create new branch id if (get_phone()->authorize(user_config, req, resp)) { resend_request(req_out_invite); // Reset state in case a 100 Trying was received state = DS_W4INVITE_RESP; return true; } return false; } bool t_dialog::resend_invite_unsupported(t_response *resp) { t_user *user_config = phone_user->get_user_profile(); if (!req_out_invite) return false; if (resp->code != R_420_BAD_EXTENSION) return false; if (!resp->hdr_unsupported.is_populated()) return false; if (resp->hdr_unsupported.features.empty()) return false; t_request *req = req_out_invite->get_request(); // If no extensions were required then return. if (!req->hdr_require.is_populated()) return false; if (req->hdr_require.features.empty()) return false; bool removed_ext = false; for (list::iterator i = resp->hdr_unsupported.features.begin(); i != resp->hdr_unsupported.features.end(); i++) { if (req->hdr_require.contains(*i)) { if (*i == EXT_100REL) { if (user_config->get_ext_100rel() == EXT_PREFERRED) { req->hdr_require.del_feature(*i); } else { // The 100rel is required. return false; } } else { // There is no specific requirement for // this extension so do not remove it. return false; } removed_ext = true; } } // Return if none of the unsupported extensions was required. if (!removed_ext) return false; if (req->hdr_require.features.empty()) { // There are no required features anymore req->hdr_require.unpopulate(); } resend_request(req_out_invite); // Reset state in case a 100 Trying was received state = DS_W4INVITE_RESP; return true; } bool t_dialog::redirect_invite(t_response *resp) { t_contact_param contact; t_user *user_config = phone_user->get_user_profile(); if (!req_out_invite) return false; // If the response is a 3XX response then add redirection contacts if (resp->get_class() == R_3XX && resp->hdr_contact.is_populated()) { req_out_invite->redirector.add_contacts( resp->hdr_contact.contact_list); } // Get next destination if (!req_out_invite->redirector.get_next_contact(contact)) { // There is no next destination return false; } assert(state == DS_W4INVITE_RESP || state == DS_W4INVITE_RESP2); t_request *req = req_out_invite->get_request(); // Ask user for permission to redirect if indicated by user config if (user_config->get_ask_user_to_redirect()) { if(!ui->cb_ask_user_to_redirect_invite(user_config, contact.uri, contact.display)) { // User did not permit to redirect return false; } } // Change the request URI to the new URI. // As the URI changes the destination set must be recalculated req->uri = contact.uri; req->calc_destinations(*user_config); ui->cb_redirecting_request(user_config, line->get_line_number(), contact); resend_request(req_out_invite); // Reset state in case a 100 Trying was received state = DS_W4INVITE_RESP; return true; } bool t_dialog::failover_invite(void) { if (!req_out_invite) return false; log_file->write_report("Failover to next destination.", "t_dialog::failover_invite"); t_request *req = req_out_invite->get_request(); // Get next destination if (!req->next_destination()) { log_file->write_report("No next destination for failover.", "t_dialog::failover_invite"); return false; } assert(state == DS_W4INVITE_RESP || state == DS_W4INVITE_RESP2); resend_request(req_out_invite); // Reset state in case a 100 Trying was received state = DS_W4INVITE_RESP; return true; } void t_dialog::send_bye(void) { t_user *user_config = phone_user->get_user_profile(); switch (state) { case DS_W4INVITE_RESP2: case DS_EARLY: case DS_CONFIRMED: break; case DS_W4RE_INVITE_RESP: case DS_W4RE_INVITE_RESP2: // send BYE after completion of re-INVITE request_cancelled = true; return; case DS_W4ACK: case DS_W4ACK_RE_INVITE: // send BYE after completion of re-INVITE request_cancelled = true; return; case DS_TERMINATED: // Dialog has already been terminated. Do not send BYE. return; default: log_file->write_header("t_dialog::failover_invite", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Cannot send BYE on dialog in state "); log_file->write_raw(state); log_file->write_endl(); log_file->write_footer(); return; } // If a previous request is still pending then remove it. if (req_out) { MEMMAN_DELETE(req_out); delete (req_out); } t_request *bye = create_request(BYE); req_out = new t_client_request(user_config, bye, 0); MEMMAN_NEW(req_out); // Trigger call script t_call_script script(user_config, t_call_script::TRIGGER_LOCAL_RELEASE, line->get_line_number() + 1); script.exec_notify(bye); line->send_request(bye, req_out->get_tuid()); line->call_hist_record.end_call(false); MEMMAN_DELETE(bye); delete bye; state = DS_W4BYE_RESP; ui->cb_call_ended(line->get_line_number()); } void t_dialog::send_options(void) { t_user *user_config = phone_user->get_user_profile(); // Request can only be sent in a confirmed dialog. if (state != DS_CONFIRMED) return; // If a previous request is still pending then remove it. if (req_out) { MEMMAN_DELETE(req_out); delete (req_out); } t_request *r = create_request(OPTIONS); // Accept r->hdr_accept.add_media(t_media("application","sdp")); req_out = new t_client_request(user_config, r, 0); MEMMAN_NEW(req_out); line->send_request(r, req_out->get_tuid()); MEMMAN_DELETE(r); delete r; } void t_dialog::send_cancel(bool early_dialog_exists) { t_request *cancel; t_user *user_config = phone_user->get_user_profile(); switch (state) { case DS_W4INVITE_RESP: case DS_W4RE_INVITE_RESP: if (!early_dialog_exists) { // wait for first response then send CANCEL or BYE request_cancelled = true; break; } // Fall through case DS_W4INVITE_RESP2: case DS_W4RE_INVITE_RESP2: case DS_EARLY: if (req_cancel) { // CANCEL has been sent already break; } cancel = create_request(CANCEL); req_cancel = new t_client_request(user_config, cancel, 0); MEMMAN_NEW(req_cancel); line->send_request(cancel, req_cancel->get_tuid()); MEMMAN_DELETE(cancel); delete cancel; // Make sure dialog is terminated if CANCEL glares with // 2XX on INVITE. set_end_after_2xx_invite(true); break; default: break; } ui->cb_call_ended(line->get_line_number()); } void t_dialog::set_end_after_2xx_invite(bool on) { end_after_2xx_invite = on; } void t_dialog::send_re_invite(void) { assert(session_re_invite); t_user *user_config = phone_user->get_user_profile(); // Request can only be sent in a confirmed dialog. if (state != DS_CONFIRMED) return; // Do nothing if a re-INVITE is already in progress if (req_out_invite) return; t_request *r = create_request(INVITE); // Set Contact header // INVITE must contain a contact header t_contact_param contact; contact.uri.set_url(line->create_user_contact(h_ip2str(r->get_local_ip()))); r->hdr_contact.add_contact(contact); // RFC 3261 13.2.1 // Allow and Supported headers SET_HDR_ALLOW(r->hdr_allow, user_config); SET_HDR_SUPPORTED(r->hdr_supported, user_config); // Extensions specific for INVITE if (user_config->get_ext_100rel() != EXT_DISABLED) { // If some weird far end implementation wants to send // a reliable provisional then support it. // As a provisional response not needed for a re-INVITE, // do not require the 100rel. r->hdr_supported.add_feature(EXT_100REL); } // Create SDP offer session_re_invite->create_sdp_offer(r, SDP_O_USER); // Send INVITE req_out_invite = new t_client_request(user_config, r, 0); MEMMAN_NEW(req_out_invite); line->send_request(r, req_out_invite->get_tuid()); MEMMAN_DELETE(r); delete r; state = DS_W4RE_INVITE_RESP; } bool t_dialog::resend_request_auth(t_response *resp) { t_client_request **current_cr; switch (resp->hdr_cseq.method) { case INVITE: // re-INVITE if (!req_out_invite) return false; assert(state == DS_W4RE_INVITE_RESP || state == DS_W4RE_INVITE_RESP2); current_cr = &req_out_invite; break; case PRACK: if (!req_prack) return false; current_cr = &req_prack; break; case REFER: if (!req_refer) return false; current_cr = &req_refer; break; case INFO: if (!req_info) return false; current_cr = &req_info; break; case SUBSCRIBE: case NOTIFY: if (!sub_refer) return false; if (!sub_refer->req_out) return false; current_cr = &(sub_refer->req_out); break; default: // other requests if (!req_out) return false; current_cr = &req_out; } if (t_abstract_dialog::resend_request_auth(*current_cr, resp)) { if (resp->hdr_cseq.method == INVITE) { // Reset state in case a 100 Trying was received state = DS_W4RE_INVITE_RESP; } return true; } return false; } bool t_dialog::redirect_request(t_response *resp) { t_client_request **current_cr; t_user *user_config = phone_user->get_user_profile(); if (resp->hdr_cseq.method == INVITE) { // re-INVITE if (!req_out_invite) return false; assert(state == DS_W4RE_INVITE_RESP || state == DS_W4RE_INVITE_RESP2); current_cr = &req_out_invite; } else { // non-INVITE if (!req_out) return false; current_cr = &req_out; } t_contact_param contact; if (!t_abstract_dialog::redirect_request(*current_cr, resp, contact)) return false; // Re-INVITE if (resp->hdr_cseq.method == INVITE) { // Reset state in case a 100 Trying was received state = DS_W4RE_INVITE_RESP; } ui->cb_redirecting_request(user_config, line->get_line_number(), contact); return true; } bool t_dialog::failover_request(t_response *resp) { t_client_request **current_cr; if (resp->hdr_cseq.method == INVITE) { // re-INVITE if (!req_out_invite) return false; assert(state == DS_W4RE_INVITE_RESP || state == DS_W4RE_INVITE_RESP2); current_cr = &req_out_invite; } else { // non-INVITE if (!req_out) return false; current_cr = &req_out; } if (!t_abstract_dialog::failover_request(*current_cr)) return false; // Re-INVITE if (resp->hdr_cseq.method == INVITE) { // Reset state in case a 100 Trying was received state = DS_W4RE_INVITE_RESP; } return true; } void t_dialog::hold(bool rtponly) { assert(!session_re_invite); // Stop glare retry timer if (id_glare_retry) { line->stop_timer(LTMR_GLARE_RETRY, get_object_id()); } reinvite_purpose = REINVITE_HOLD; if (rtponly) { session->stop_rtp(); session->hold(); // Stopping the RTP only is like a full call hold where // the re-INVITE failed. By setting the hold_failed flag, // a subsequent retrieve will only start RTP. hold_failed = true; return; } hold_failed = false; session_re_invite = session->create_call_hold(); send_re_invite(); // Stop the audio streams now. If we do not stop the stream now // the stream will be stopped when a 200 OK is received on the // re-INVITE. However, when the line is put on-hold because // the user switches to another line that already has a held call // a race condition might occur: // // 1. A re-INVITE on this line is sent to put it on-hold // 2. A re-INVITE on the other line is sent to retrieve the call // 3. If the 200 OK on the second re-INVITE comes in before the // the 200 OK on the first re-INVITE, then the audio streams // for the second line will be started already while the first // line still has the audio device open. On some systems this // causes a dead lock as the audio device may only be opened // once. // // Also if the re-INVITE to put the line on-hold fails, the // audio might not be stopped at all. It must be stopped however // as the user has switched to the other line. So stopping the // audio now will make sure the audio device is idle when the // second call is retrieved. session->stop_rtp(); // Prevent RTP stream from getting started even if the signaling // for hold fails. After all the user has put the phone locally // on-hold, so RTP should never be started. session->hold(); } void t_dialog::retrieve(void) { assert(!session_re_invite); t_user *user_config = phone_user->get_user_profile(); // Stop glare retry timer if (id_glare_retry) { line->stop_timer(LTMR_GLARE_RETRY, get_object_id()); } // Allow RTP stream to be started again. session->unhold(); // If the previous call-hold failed, then only RTP needs to // be restarted. The session description did never change // because of the failure. if (hold_failed) { session->start_rtp(); return; } // If STUN is enabled, then first send a STUN binding request to // discover the IP adderss and port for media. if (phone->use_stun(user_config)) { if (!stun_bind_media()) { // No re-INVITE can be sent. Simply return. // User will decide if the call should be // torn down. return; } } reinvite_purpose = REINVITE_RETRIEVE; session_re_invite = session->create_call_retrieve(); send_re_invite(); } void t_dialog::kill_rtp(void){ session->kill_rtp(); if (session_re_invite) session_re_invite->kill_rtp(); } void t_dialog::send_refer(const t_url &uri, const string &display) { t_user *user_config = phone_user->get_user_profile(); if (state != DS_CONFIRMED) return; if (refer_state != REFST_NULL) return; // If a previous refer is still in progress, then do nothing if (req_refer) { log_file->write_report("A REFER request is already in progress.", "t_dialog::send_refer"); return; } // If a refer subscription already exists, then do nothing if (sub_refer) { log_file->write_report("Refer subscription exists already.", "t_dialog::send_refer"); return; } t_request *refer = create_request(REFER); // Refer-To header refer->hdr_refer_to.set_uri(uri); refer->hdr_refer_to.set_display(display); // Referred-By header refer->hdr_referred_by.set_uri(line->create_user_uri()); refer->hdr_referred_by.set_display(user_config->get_display(line->get_hide_user())); req_refer = new t_client_request(user_config, refer, 0); MEMMAN_NEW(req_refer); line->send_request(refer, req_refer->get_tuid()); MEMMAN_DELETE(refer); delete refer; refer_succeeded = false; out_refer_req_failed = false; refer_state = REFST_W4RESP; } void t_dialog::send_dtmf(char digit, bool inband, bool info) { t_user *user_config = phone_user->get_user_profile(); if (info) { if (req_info) { // An INFO request is still in progress, put the // DTMF digit in the queue dtmf_queue.push(digit); } else { t_request *info_request = create_request(INFO); // Content-Type header info_request->hdr_content_type.set_media(t_media("application", "dtmf-relay")); // application/dtmf-relay body info_request->body = new t_sip_body_dtmf_relay(digit, user_config->get_dtmf_duration()); MEMMAN_NEW(info_request->body); req_info = new t_client_request(user_config, info_request, 0); MEMMAN_NEW(req_info); line->send_request(info_request, req_info->get_tuid()); MEMMAN_DELETE(info_request); delete info_request; ui->cb_send_dtmf(line->get_line_number(), char2dtmf_ev(digit)); } } else { if (session) session->send_dtmf(digit, inband); } } bool t_dialog::stun_bind_media(void) { t_user *user_config = phone_user->get_user_profile(); try { unsigned long mapped_ip; unsigned short mapped_port; int stun_err_code; string stun_err_reason; bool ret = get_stun_binding(user_config, line->get_rtp_port(), mapped_ip, mapped_port, stun_err_code, stun_err_reason); if (!ret) { // STUN request failed ui->cb_stun_failed(user_config, stun_err_code, stun_err_reason); log_file->write_header("t_dialog::stun_bind_media", LOG_NORMAL, LOG_CRITICAL); log_file->write_raw("STUN bind request for media failed.\n"); log_file->write_raw(stun_err_code); log_file->write_raw(" "); log_file->write_raw(stun_err_reason); log_file->write_endl(); log_file->write_footer(); return false; } // STUN binding request succeeded. session->receive_host = h_ip2str(mapped_ip); session->receive_port = mapped_port; } catch (int err) { // STUN request failed ui->cb_stun_failed(user_config); log_file->write_header("t_dialog::stun_bind_media", LOG_NORMAL, LOG_CRITICAL); log_file->write_raw("STUN bind request for media failed.\n"); log_file->write_raw(get_error_str(err)); log_file->write_endl(); log_file->write_footer(); return false; } return true; } void t_dialog::recvd_response(t_response *r, t_tuid tuid, t_tid tid) { t_user *user_config = phone_user->get_user_profile(); t_abstract_dialog::recvd_response(r, tuid, tid); if (r->hdr_cseq.method == INVITE && tuid == 0 && tid == 0 && !req_out_invite) { t_ip_port ip_port; // Only a retransmission of a 2XX INVITE is allowed. if (r->get_class() != R_2XX) return; if (!ack) return; if (r->hdr_cseq.seqnr != ack->hdr_cseq.seqnr) { // The 2XX response does not match the ACK return; } ack->get_destination(ip_port, *user_config); if (ip_port.ipaddr != 0 && ip_port.port != 0) { evq_sender->push_network(ack, ip_port); } return; } if (r->hdr_cseq.method == CANCEL) { if (!req_cancel) return; if (r->is_final()) { remove_client_request(&req_cancel); if (r->is_success()) { line->start_timer(LTMR_CANCEL_GUARD, get_object_id()); } else { // CANCEL request failed. ui->cb_cancel_failed(line->get_line_number(), r); // Abort the INVITE as the user cannot terminate // it in a normal way. if (req_out_invite) { t_tid _tid = req_out_invite->get_tid(); if (_tid > 0) { evq_trans_mgr->push_abort_trans(_tid); } } } } return; } // No processing done for PRACK responses. if (r->hdr_cseq.method == PRACK) { if (!req_prack) return; t_request *prack = req_prack->get_request(); if (r->hdr_cseq.seqnr != prack->hdr_cseq.seqnr) { // The response does not match the latest sent PRACK. // It might match a previous sent PRACK. However, when // a previous PRACK fails, then the latest PRACK will also // fail, so the failure will be handled in the end without // the overhead to keep a list of all pending PRACKs which // should be a rare case. return; } if (r->is_final()) { // PRACK is finished, so remove request remove_client_request(&req_prack); // Tear down the call if PRACK failed and call is // not yet established. if (!r->is_success() && state == DS_EARLY) { log_file->write_header("t_dialog::recvd_response", LOG_NORMAL, LOG_WARNING); log_file->write_raw("PRACK failed: "); log_file->write_raw(r->code); log_file->write_raw(" "); log_file->write_raw(r->reason); log_file->write_endl(); log_file->write_raw("Call will be cancelled.\n"); log_file->write_footer(); ui->cb_prack_failed(line->get_line_number(), r); send_cancel(true); // Ignore the failure in other states. // The call has been setup, so all seems fine. } } return; } // Determine if this is an INVITE or non-INVITE response t_client_request *req; bool send_to_sub_refer = false; switch(r->hdr_cseq.method) { case INVITE: req = req_out_invite; break; case SUBSCRIBE: case NOTIFY: if (!sub_refer) return; req = sub_refer->req_out; send_to_sub_refer = true; break; case REFER: req = req_refer; break; case INFO: req = req_info; break; default: req = req_out; } // Discard response if no request is pending if (!req) { return; } // Check cseq if (r->hdr_cseq.method != req->get_request()->method) { return; } if (r->hdr_cseq.seqnr != req->get_request()->hdr_cseq.seqnr) return; // Set the transaction identifier. This identifier is needed if the // transaction must be aborted at a later time. req->set_tid(tid); if (send_to_sub_refer) { sub_refer->recv_response(r, tuid, tid); if (sub_refer->get_state() == SS_TERMINATED) { MEMMAN_DELETE(sub_refer); delete sub_refer; sub_refer = NULL; if (state == DS_CONFIRMED_SUB) { state = DS_TERMINATED; } } return; } switch (state) { case DS_W4INVITE_RESP: case DS_W4INVITE_RESP2: state_w4invite_resp(r, tuid, tid); break; case DS_EARLY: state_early(r, tuid, tid); break; case DS_W4BYE_RESP: state_w4bye_resp(r, tuid, tid); break; case DS_CONFIRMED: state_confirmed_resp(r, tuid, tid); break; case DS_W4RE_INVITE_RESP: case DS_W4RE_INVITE_RESP2: state_w4re_invite_resp(r, tuid, tid); break; default: // No response expected in other states. Discard. break; } } void t_dialog::recvd_request(t_request *r, t_tuid tuid, t_tid tid) { t_response *resp; t_user *user_config = phone_user->get_user_profile(); // CANCEL will be handled by recvd_cancel() t_abstract_dialog::recvd_request(r, tuid, tid); switch (r->method) { case ACK: // When ACK is received then the current incoming request // must be INVITE. if (!req_in_invite) return; if (req_in_invite->get_request()->hdr_cseq.seqnr != r->hdr_cseq.seqnr) { log_file->write_header("t_dialog::recvd_request", LOG_NORMAL, LOG_WARNING); log_file->write_raw("ACK does not match a pending INVITE.\n"); log_file->write_raw("Discard ACK.\n"); log_file->write_footer(); return; } break; case INVITE: if (remote_seqnr_set && r->hdr_cseq.seqnr <= remote_seqnr) { // Request received out of sequence. Discard. log_file->write_header("t_dialog::recvd_request", LOG_NORMAL, LOG_WARNING); log_file->write_raw("INVITE is received out of order.\n"); log_file->write_raw("Remote seqnr = "); log_file->write_raw(remote_seqnr); log_file->write_endl(); log_file->write_raw("Received seqnr = "); log_file->write_raw(r->hdr_cseq.seqnr); log_file->write_endl(); log_file->write_raw("Discard INVITE.\n"); log_file->write_footer(); return; } remote_seqnr = r->hdr_cseq.seqnr; remote_seqnr_set = true; if (req_in_invite) { // RFC 3261 14.2 // Another INVITE is received while the previous // one is not finished. resp = r->create_response(R_500_INTERNAL_SERVER_ERROR, "Previous INVITE still in progress"); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; return; } else if (req_out_invite) { // RFC 3261 14.2 // re-INVITE glare resp = r->create_response(R_491_REQUEST_PENDING); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; return; } else { req_in_invite = new t_client_request(user_config, r, tid); MEMMAN_NEW(req_in_invite); } break; case REFER: // Reset refer_accepted indication. refer_accepted = false; // fall thru default: // Check cseq // RFC 3261 12.2.2 if (remote_seqnr_set && r->hdr_cseq.seqnr <= remote_seqnr) { // Request received out of order. log_file->write_header("t_dialog::recvd_request", LOG_NORMAL, LOG_WARNING); log_file->write_raw("CSeq seqnr is out of sequence.\n"); log_file->write_raw("Reveived seqnr: "); log_file->write_raw(r->hdr_cseq.seqnr); log_file->write_endl(); log_file->write_raw("Remote seqnr: "); log_file->write_raw(remote_seqnr); log_file->write_endl(); log_file->write_footer(); resp = r->create_response(R_500_INTERNAL_SERVER_ERROR, "Request received out of order"); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; return; } remote_seqnr = r->hdr_cseq.seqnr; remote_seqnr_set = true; } t_dialog_state old_state = state; switch (state) { case DS_NULL: state_null(r, tuid, tid); break; case DS_W4ACK: state_w4ack(r, tuid, tid); break; case DS_W4ACK_RE_INVITE: state_w4ack_re_invite(r, tuid, tid); break; case DS_W4ANSWER: state_w4answer(r, tuid, tid); break; case DS_W4RE_INVITE_RESP: case DS_W4RE_INVITE_RESP2: state_w4re_invite_resp(r, tuid, tid); break; case DS_W4BYE_RESP: state_w4bye_resp(r, tuid, tid); break; case DS_CONFIRMED: state_confirmed(r, tuid, tid); break; case DS_CONFIRMED_SUB: state_confirmed_sub(r, tuid, tid); break; default: // No request expected in other states. Discard. resp = r->create_response(R_500_INTERNAL_SERVER_ERROR); line->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; break; } // If the state has changed, then waiting requests needs to be // processed. if (state != old_state && !inc_req_queue.empty()) { t_client_request *queued_cr = inc_req_queue.front(); inc_req_queue.pop_front(); log_file->write_header("t_dialog::recvd_request", LOG_NORMAL, LOG_INFO); log_file->write_raw("Process queued "); log_file->write_raw(method2str(r->method, r->unknown_method)); log_file->write_endl(); log_file->write_footer(); recvd_request(queued_cr->get_request(), 0, queued_cr->get_tid()); MEMMAN_DELETE(queued_cr); delete queued_cr; } } // RFC 3261 9.2 void t_dialog::recvd_cancel(t_request *r, t_tid cancel_tid, t_tid target_tid) { t_response *resp; assert(r->method == CANCEL); // Send 200 as response to CANCEL resp = r->create_response(R_200_OK); // RFC 3261 9.2 // The To-tag in the response to the CANCEL should be the same // as the To-tag in the original request. resp->hdr_to.set_tag(local_tag); line->send_response(resp, 0, cancel_tid); MEMMAN_DELETE(resp); delete resp; switch (state) { case DS_W4ANSWER: state_w4answer(r, 0, cancel_tid); break; default: // Ignore CANCEL in other states. break; } } void t_dialog::recvd_stun_resp(StunMessage *r, t_tuid tuid, t_tid tid) { // Not used anymore. // STUN requests are performed in a synchronous way. } // RFC 3261 13.3.1.4 void t_dialog::answer(void) { t_user *user_config = phone_user->get_user_profile(); if (!req_in_invite) return; t_request *invite_req = req_in_invite->get_request(); // RFC 3262 3 // Delay the final response if we are still waiting for a PRACK // on a 1xx response containing SDP if (resp_1xx_invite && resp_1xx_invite->body) { answer_after_prack = true; return; } if (state != DS_W4ANSWER) { throw X_WRONG_STATE; } resp_invite = invite_req->create_response(R_200_OK); resp_invite->hdr_to.set_tag(local_tag); // Set Organization header SET_HDR_ORGANIZATION(resp_invite->hdr_organization, user_config); // RFC 3261 12.1.1 // Copy the Record-Route header from request to response if (invite_req->hdr_record_route.is_populated()) { resp_invite->hdr_record_route = invite_req->hdr_record_route; } // Set Contact header t_contact_param contact; contact.uri.set_url(line->create_user_contact(h_ip2str(resp_invite->get_local_ip()))); resp_invite->hdr_contact.add_contact(contact); // Set Allow and Supported headers SET_HDR_ALLOW(resp_invite->hdr_allow, user_config); SET_HDR_SUPPORTED(resp_invite->hdr_supported, user_config); // RFC 3261 13.3.1.4 // Create SDP offer if no offer was received in INVITE and no offer // was sent in a reliable 1xx response (RFC 3262 5) // Otherwise if no offer was sent in a reliable 1xx, create an SDP answer. if (!session->sent_offer) { if (!session->recvd_offer && !session->sent_offer) { session->create_sdp_offer(resp_invite, SDP_O_USER); } else { session->create_sdp_answer(resp_invite, SDP_O_USER); session->start_rtp(); } } // Trigger call script t_call_script script(user_config, t_call_script::TRIGGER_IN_CALL_ANSWERED, line->get_line_number() + 1); script.exec_notify(resp_invite); line->call_hist_record.answer_call(resp_invite); line->send_response(resp_invite, req_in_invite->get_tuid(), req_in_invite->get_tid()); line->start_timer(LTMR_ACK_GUARD, get_object_id()); line->start_timer(LTMR_ACK_TIMEOUT, get_object_id()); // Stop 100rel timers if they are running. line->stop_timer(LTMR_100REL_GUARD, get_object_id()); line->stop_timer(LTMR_100REL_TIMEOUT, get_object_id()); state = DS_W4ACK; } void t_dialog::reject(int code, string reason) { t_response *resp; t_user *user_config = phone_user->get_user_profile(); if (state != DS_W4ANSWER) { throw X_WRONG_STATE; } assert(req_in_invite); assert(code >= 400); resp = req_in_invite->get_request()->create_response(code, reason); resp->hdr_to.set_tag(local_tag); // Trigger call script t_call_script script(user_config, t_call_script::TRIGGER_IN_CALL_FAILED, line->get_line_number() + 1); script.exec_notify(resp); line->send_response(resp, req_in_invite->get_tuid(), req_in_invite->get_tid()); line->call_hist_record.fail_call(resp); MEMMAN_DELETE(resp); delete resp; // Stop 100rel timers if they are running. line->stop_timer(LTMR_100REL_GUARD, get_object_id()); line->stop_timer(LTMR_100REL_TIMEOUT, get_object_id()); state = DS_TERMINATED; } void t_dialog::redirect(const list &destinations, int code, string reason) { t_response *resp; t_user *user_config = phone_user->get_user_profile(); if (state != DS_W4ANSWER) { throw X_WRONG_STATE; } assert(req_in_invite); assert(code >= 300 && code <= 399); resp = req_in_invite->get_request()->create_response(code, reason); resp->hdr_to.set_tag(local_tag); t_contact_param *contact; float q = 0.9; for (list::const_iterator i = destinations.begin(); i != destinations.end(); i++) { contact = new t_contact_param(); MEMMAN_NEW(contact); contact->display = i->display; contact->uri = i->url; contact->set_qvalue(q); resp->hdr_contact.add_contact(*contact); MEMMAN_DELETE(contact); delete contact; q = q - 0.1; if (q < 0.1) q = 0.1; } // Trigger call script t_call_script script(user_config, t_call_script::TRIGGER_IN_CALL_FAILED, line->get_line_number() + 1); script.exec_notify(resp); line->send_response(resp, req_in_invite->get_tuid(), req_in_invite->get_tid()); line->call_hist_record.fail_call(resp); MEMMAN_DELETE(resp); delete resp; // Stop 100rel timers if they are running. line->stop_timer(LTMR_100REL_GUARD, get_object_id()); line->stop_timer(LTMR_100REL_TIMEOUT, get_object_id()); state = DS_TERMINATED; } bool t_dialog::match_response(t_response *r, t_tuid tuid) { if (tuid != 0) { if (req_out && req_out->get_tuid() == tuid) return true; if (req_out_invite && req_out_invite->get_tuid() == tuid) { return true; } if (req_cancel && req_cancel->get_tuid() == tuid) { return true; } return false; } // The implementation sends CANCEL on the open dialog. // The tags of a CANCEL response will be identical to the tags of // the INVITE, so it matches all pending dialogs as well. // So a CANCEL should only match if the dialog has a CANCEL request // pending. if (r->hdr_cseq.method == CANCEL && !req_cancel) return false; return t_abstract_dialog::match_response(r, tuid); } bool t_dialog::match_response(StunMessage *r, t_tuid tuid) { if (tuid == 0) return false; if (!req_stun) return false; return (req_stun->get_tuid() == tuid); } bool t_dialog::match_cancel(t_request *r, t_tid target_tid) { return (req_in_invite && req_in_invite->get_tid() == target_tid); } bool t_dialog::is_invite_retrans(t_request *r) { assert(r->method == INVITE); // An INVITE can only be a retransmission if an incoming INVITE is // still in progress. if (!req_in_invite) return false; t_request *request = req_in_invite->get_request(); // RFC 3261 17.2.3 t_via &orig_top_via = request->hdr_via.via_list.front(); t_via &recv_top_via = r->hdr_via.via_list.front(); if (recv_top_via.rfc3261_compliant()) { if (orig_top_via.branch != recv_top_via.branch) return false; if (orig_top_via.host != recv_top_via.host) return false; if (orig_top_via.port != recv_top_via.port) return false; return (request->hdr_cseq.method == r->hdr_cseq.method); } // Matching rules for backward compatibiliy with RFC 2543 // TODO: verify rules for matching via headers return (request->uri.sip_match(r->uri) && request->hdr_to.tag == r->hdr_to.tag && request->hdr_from.tag == r->hdr_from.tag && request->hdr_call_id.call_id == r->hdr_call_id.call_id && request->hdr_cseq.seqnr == r->hdr_cseq.seqnr && orig_top_via.host == recv_top_via.host && orig_top_via.port == recv_top_via.port); } void t_dialog::process_invite_retrans(void) { t_ip_port ip_port; // Retransmit 2xx response. // Send the response directly to the sender thread // as the INVITE transaction completed already. // (see RFC 3261 17.2.1) if (!resp_invite) return; // there is no response to send resp_invite->get_destination(ip_port); if (ip_port.ipaddr == 0) { // This should not happen. The response has been // sent before so it should be possible to sent // it again. Ignore the timeout. When the ACK // guard timer expires, the dialog will be // cleaned up. return; } evq_sender->push_network(resp_invite, ip_port); } t_dialog_state t_dialog::get_state(void) const { return state; } void t_dialog::timeout(t_line_timer timer) { switch(state) { case DS_W4INVITE_RESP: case DS_W4INVITE_RESP2: state_w4invite_resp(timer); break; case DS_EARLY: state_early(timer); break; case DS_W4ACK: state_w4ack(timer); break; case DS_W4ACK_RE_INVITE: state_w4ack_re_invite(timer); break; case DS_W4RE_INVITE_RESP2: state_w4re_invite_resp(timer); break; case DS_W4ANSWER: state_w4answer(timer); break; case DS_CONFIRMED: state_confirmed(timer); break; default: // Timeout not expected in other states. Ignore. break; } } void t_dialog::timeout_sub(t_subscribe_timer timer, const string &event_type, const string &event_id) { if (sub_refer && sub_refer->get_event_type() == event_type && sub_refer->get_event_id() == event_id) { sub_refer->timeout(timer); } else { // Timeout does not match with the current subscription. // Ignore. return; } if (sub_refer->get_state() == SS_TERMINATED && state == DS_CONFIRMED_SUB) { MEMMAN_DELETE(sub_refer); delete sub_refer; sub_refer = NULL; state = DS_TERMINATED; } } t_phone *t_dialog::get_phone(void) const { return line->get_phone(); } t_line *t_dialog::get_line(void) const { return line; } t_session *t_dialog::get_session(void) const { return session; } t_audio_session *t_dialog::get_audio_session(void) const { if (!session) return NULL; return session->get_audio_session(); } bool t_dialog::has_active_session(void) const { if (session) return session->is_rtp_active(); return false; } // RFC 3515 // Send a NOTIFY with reference progress to the referror void t_dialog::notify_refer_progress(t_response *r) { if (!sub_refer) return; if (r->is_final()) { sub_refer->send_notify(r, SUBSTATE_TERMINATED, EV_REASON_NORESOURCE); } else { sub_refer->send_notify(r, SUBSTATE_ACTIVE); } } bool t_dialog::will_release(void) const { return state == DS_W4BYE_RESP || request_cancelled || end_after_2xx_invite || end_after_ack; } twinkle-1.10.1/src/dialog.h000066400000000000000000000642221277565361200154720ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * SIP dialog established by an INVITE transaction. */ #ifndef _DIALOG_H #define _DIALOG_H #include #include #include #include #include "abstract_dialog.h" #include "client_request.h" #include "phone.h" #include "transaction_layer.h" #include "protocol.h" #include "redirect.h" #include "session.h" #include "user.h" #include "sockets/url.h" #include "threads/mutex.h" #include "parser/request.h" #include "sdp/sdp.h" #include "stun/stun.h" using namespace std; // Forward declarations class t_phone; class t_line; class t_session; class t_sub_refer; /** Dialog state */ enum t_dialog_state { DS_NULL, /**< Initial state */ // UAC states DS_W4INVITE_RESP, /**< INVITE sent, waiting for response */ DS_W4INVITE_RESP2, /**< Provisional response received */ DS_EARLY, /**< Provisional response with to-tag received */ DS_W4BYE_RESP, /**< BYE sent, waiting for response */ // UAS states DS_W4ACK, /**< Waiting for ACK on 2XX INVITE */ DS_W4ANSWER, /**< INVITE received, waiting for user to answer */ // UAS and UAC states DS_CONFIRMED, /**< Success received/sent */ DS_W4ACK_RE_INVITE, /**< Waiting for ACK on re-INVITE */ DS_W4RE_INVITE_RESP, /**< re-INVITE sent, waiting for response */ DS_W4RE_INVITE_RESP2, /**< re-INVITE sent, provisional response recvd */ DS_TERMINATED, /**< Dialog terminated */ // Subscription states DS_CONFIRMED_SUB, /**< Confirmed refer-subscription dialog */ }; /** Purpose for sending a re-INVITE request */ enum t_reinvite_purpose { REINVITE_HOLD, /**< Re-invite for call hold */ REINVITE_RETRIEVE, /**< Re-invite for call retrieve */ }; /** * SIP dialog established by an INVITE transaction. * * Dialog state diagrams: * @dot * digraph call { * label="Call setup and tear down state transitions" * node [shape=ellipse, fontname=Helvetica, fontsize=10, style=filled, fillcolor=yellow]; * edge [fontname=Helvetica, fontsize=9]; * * null [label="DS_NULL" URL="\ref DS_NULL"]; * w4invite_resp [label="DS_W4INVITE_RESP" URL="\ref DS_W4INVITE_RESP"] * w4invite_resp2 [label="DS_W4INVITE_RESP2" URL="\ref DS_W4INVITE_RESP2"] * early [label="DS_EARLY" URL="\ref DS_EARLY"] * w4bye_resp [label="DS_W4BYE_RESP" URL="\ref DS_W4BYE_RESP"] * w4ack [label="DS_W4ACK" URL="\ref DS_W4ACK"] * w4answer [label="DS_W4ANSWER" URL="\ref DS_W4ANSWER"] * confirmed [label="DS_CONFIRMED" URL="\ref DS_CONFIRMED"] * terminated [label="DS_TERMINATED" URL="\ref DS_TERMINATED"] * * null -> w4invite_resp [label="send INVITE"] * null -> w4answer [label="receive INVITE"] * null -> terminated [label="receive INVITE\nSTUN media\nbind fails"] * null -> terminated [label="receive INVITE\nunsupported\nmedia or body"] * w4invite_resp -> w4invite_resp2 [label="receive 1XX without to-tag"] * w4invite_resp -> early [label="receive 1XX with to-tag"] * w4invite_resp -> confirmed [label="receive 2XX"] * w4invite_resp -> terminated [label="receive failure\nresponse"] * w4invite_resp2 -> confirmed [label="receive 2XX"] * w4invite_resp2 -> early [label="receive 1XX with to-tag"] * w4invite_resp2 -> terminated [label="receive failure\nresponse"] * early -> confirmed [label="receive 2XX"] * early -> terminated [label="receive failure\nresponse"] * w4answer -> w4ack [label="user answered, send 2XX"] * w4answer -> terminated [label="user rejected\nsend 4XX/6XX"] * w4answer -> terminated [label="receive CANCEL/BYE\nsend 487"] * w4ack -> confirmed [label="receive ACK"] * confirmed -> w4bye_resp [label="send BYE"] * confirmed -> terminated [label="receive BYE"] * w4bye_resp -> terminated [label="receive BYE response"] * } * @enddot * * @dot * digraph reinvite { * label="re-INVITE state transitions" * node [shape=ellipse, fontname=Helvetica, fontsize=10, style=filled, fillcolor=yellow]; * edge [fontname=Helvetica, fontsize=9]; * * confirmed [label="DS_CONFIRMED" URL="\ref DS_CONFIRMED"] * w4ack_re_invite [label="DS_W4ACK_RE_INVITE" URL="\ref DS_W4ACK_RE_INVITE"] * w4re_invite_resp [label="DS_W4RE_INVITE_RESP" URL="\ref DS_W4RE_INVITE_RESP"] * w4re_invite_resp2 [label="DS_W4RE_INVITE_RESP2" URL="\ref DS_W4RE_INVITE_RESP2"] * terminated [label="DS_TERMINATED" URL="\ref DS_TERMINATED"] * * confirmed -> w4ack_re_invite [label="receive re-INVITE, send 2XX"] * confirmed -> terminated [label="receive re-INVITE\nSTUN fails"] * confirmed -> confirmed [label="receive re-INVITE, send failure response"] * confirmed -> w4re_invite_resp [label="send re-INVITE"] * w4ack_re_invite -> confirmed [label="reveive ACK"] * w4re_invite_resp -> w4re_invite_resp2 [label="receive 1XX"] * w4re_invite_resp -> confirmed [label="receive final response"] * w4re_invite_resp2 -> confirmed [label="receive final response"] * w4re_invite_resp -> terminated [label="receive BYE"] * w4re_invite_resp2 -> terminated [label="receive BYE"] * } * @enddot * * @dot * digraph refer { * label="State transitions when REFER subscription is active" * node [shape=ellipse, fontname=Helvetica, fontsize=10, style=filled, fillcolor=yellow]; * edge [fontname=Helvetica, fontsize=9]; * * confirmed [label="DS_CONFIRMED" URL="\ref DS_CONFIRMED"] * w4bye_resp [label="DS_W4BYE_RESP" URL="\ref DS_W4BYE_RESP"] * w4re_invite_resp [label="DS_W4RE_INVITE_RESP" URL="\ref DS_W4RE_INVITE_RESP"] * w4re_invite_resp2 [label="DS_W4RE_INVITE_RESP2" URL="\ref DS_W4RE_INVITE_RESP2"] * terminated [label="DS_TERMINATED" URL="\ref DS_TERMINATED"] * confirmed_sub [label="DS_CONFIRMED_SUB" URL="DS_CONFIRMED_SUB"] * * confirmed -> confirmed_sub [label="receive BYE"] * w4re_invite_resp -> confirmed_sub [label="receive BYE"] * w4re_invite_resp2 -> confirmed_sub [label="receive BYE"] * w4bye_resp -> confirmed_sub [label="receive BYE response"] * confirmed_sub -> terminated [label="terminate subscription"] * } * @enddot * * @dot * digraph timeout { * label="State transitions due to timeouts" * node [shape=ellipse, fontname=Helvetica, fontsize=10, style=filled, fillcolor=yellow]; * edge [fontname=Helvetica, fontsize=9]; * * w4answer [label="DS_W4ANSWER" URL="\ref DS_W4ANSWER"] * w4ack [label="DS_W4ACK" URL="\ref DS_W4ACK"] * confirmed [label="DS_CONFIRMED" URL="\ref DS_CONFIRMED"] * terminated [label="DS_TERMINATED" URL="\ref DS_TERMINATED"] * confirmed_sub [label="DS_CONFIRMED_SUB" URL="DS_CONFIRMED_SUB"] * * w4answer -> w4answer [label="LTMR_100REL_TIMEOUT\nretransmit 1XX" URL="LTMR_100REL_TIMEOUT"] * w4answer -> terminated [label="LTMR_100REL_GUARD\nsend 500" URL="LTMR_100REL_GUARD"] * w4ack -> w4ack [label="LTMR_ACK_TIMEOUT\nretransmit 2XX" URL="LTMR_ACK_TIMEOUT"] * w4ack -> confirmed [label="LTMR_ACK_GUARD\ntear down call" URL="LTMR_ACK_GUARD"] * confirmed_sub -> terminated [label="REFER subscription\ntimeout"] * } * @enddot */ class t_dialog : public t_abstract_dialog { friend class t_phone; protected: t_line *line; /**< Phone line owning this dialog. */ t_dialog_state state; /**< Dialog state. */ /** Session established by this dialog. */ t_session *session; /** * New session being established during re-invite. * When the re-INVITE transaction finishes successfully, then * this session information will override the general session. */ t_session *session_re_invite; /** The purpose of an outgoing re-INVITE request */ t_reinvite_purpose reinvite_purpose; /** Indicates if the last call hold action failed. */ bool hold_failed; t_client_request *req_out; /**< Pending outgoing non-INVITE request */ t_client_request *req_out_invite; /**< Pending outgoing INVITE */ t_client_request *req_in_invite; /**< Pending incoming INVITE */ t_client_request *req_cancel; /**< Pending outgoing CANCEL */ t_client_request *req_refer; /**< Pending outgoing REFER */ t_client_request *req_info; /**< Pending outgoing INFO */ /** * Last outgoing PRACK. While a PRACK is still pending a new 1xx * response might come in. A PRACK will be sent for this 1xx without * waiting for the response for the previous PRACK. */ t_client_request *req_prack; /** Pending STUN request */ t_client_request *req_stun; /** * Incoming request queue. A request may come in when it cannot be * served yet. Such a request is stored in the queue to be served * later. */ list inc_req_queue; /** Indication if request must be cancelled */ bool request_cancelled; /** * Indication that the dialog must be terminated after a 2XX * on an INVITE is received (e.g. when 2XX glares with CANCEL). */ bool end_after_2xx_invite; /** Indication that the dialog must be terminated after ACK. */ bool end_after_ack; /** * Indication that the user wants to answer the call. * Sending the answer must be delayed as we are still waiting for * a PRACK to acknowledge a 1xx containing SDP from the * far end (RFC 3262 3). */ bool answer_after_prack; /** Indication if 180 ringing has already been received */ bool ringing_received; /** Cached success response to INVITE needed for retransmission */ t_response *resp_invite; /** * Cached provisional response to INVITE needed for retransmission * when provisional responses are sent reliable (100rel) */ t_response *resp_1xx_invite; /** Cached ack needed for retransmission */ t_request *ack; /** Subscription created by REFER (RFC 3515) */ t_sub_refer *sub_refer; /** Queue of DTMF digits to be sent via INFO requests */ queue dtmf_queue; /** @name Process incoming responses */ //@{ /** * Process an incoming response in the @ref DS_W4INVITE_RESP state. * @param r The response * @param tuid Transaction user id * @param tid Transaction id */ void state_w4invite_resp(t_response *r, t_tuid tuid, t_tid tid); /** * Process an incoming response in the @ref DS_EARLY state. * @param r The response * @param tuid Transaction user id * @param tid Transaction id */ void state_early(t_response *r, t_tuid tuid, t_tid tid); /** * Process an incoming response in the @ref DS_W4BYE_RESP state. * @param r The response * @param tuid Transaction user id * @param tid Transaction id */ void state_w4bye_resp(t_response *r, t_tuid tuid, t_tid tid); /** * Process an incoming response in the @ref DS_CONFIRMED state. * @param r The response * @param tuid Transaction user id * @param tid Transaction id */ void state_confirmed_resp(t_response *r, t_tuid tuid, t_tid tid); /** * Process an incoming response in the @ref DS_W4RE_INVITE_RESP state. * @param r The response * @param tuid Transaction user id * @param tid Transaction id */ void state_w4re_invite_resp(t_response *r, t_tuid tuid, t_tid tid); //@} /** @name Process incoming requests */ //@{ /** * Process an incoming request in the @ref DS_NULL state. * @param r The request * @param tuid Transaction user id * @param tid Transaction id */ void state_null(t_request *r, t_tuid tuid, t_tid tid); /** * Process an incoming request in the @ref DS_W4ANSWER state. * @param r The request * @param tuid Transaction user id * @param tid Transaction id */ void state_w4answer(t_request *r, t_tuid tuid, t_tid tid); /** * Process an incoming request in the @ref DS_W4ACK state. * @param r The request * @param tuid Transaction user id * @param tid Transaction id */ void state_w4ack(t_request *r, t_tuid tuid, t_tid tid); /** * Process an incoming request in the @ref DS_W4ACK_RE_INVITE state. * @param r The request * @param tuid Transaction user id * @param tid Transaction id */ void state_w4ack_re_invite(t_request *r, t_tuid tuid, t_tid tid); /** * Process an incoming request in the @ref DS_W4RE_INVITE_RESP state. * @param r The request * @param tuid Transaction user id * @param tid Transaction id */ void state_w4re_invite_resp(t_request *r, t_tuid tuid, t_tid tid); /** * Process an incoming request in the @ref DS_W4BYE_RESP state. * @param r The request * @param tuid Transaction user id * @param tid Transaction id */ void state_w4bye_resp(t_request *r, t_tuid tuid, t_tid tid); /** * Process an incoming request in the @ref DS_CONFIRMED state. * @param r The request * @param tuid Transaction user id * @param tid Transaction id */ void state_confirmed(t_request *r, t_tuid tuid, t_tid tid); /** * Process an incoming request in the @ref DS_CONFIRMED_SUB state. * @param r The request * @param tuid Transaction user id * @param tid Transaction id */ void state_confirmed_sub(t_request *r, t_tuid tuid, t_tid tid); //@} /** @name Process requests in the confirmed state */ //@{ /** Proces incoming re-INVITE. */ void process_re_invite(t_request *r, t_tuid tuid, t_tid tid); /** Proces incoming REFER. */ void process_refer(t_request *r, t_tuid tuid, t_tid tid); /** Process incoming SUBSCRIBE (refer subscription). */ void process_subscribe(t_request *r, t_tuid tuid, t_tid tid); /** Process incoming NOTIFY (refer subscription). */ void process_notify(t_request *r, t_tuid tuid, t_tid tid); /** Process incoming INFO. */ void process_info(t_request *r, t_tuid tuid, t_tid tid); /** Process incoming MESSAGE. */ void process_message(t_request *r, t_tuid tuid, t_tid tid); //@} /** @name Process timeouts */ //@{ /** * Process timeout in @ref DS_W4INVITE_RESP state. * @param timer The expired timer. */ void state_w4invite_resp(t_line_timer timer); /** * Process timeout in @ref DS_EARLY state. * @param timer The expired timer. */ void state_early(t_line_timer timer); /** * Process timeout in @ref DS_W4ACK state. * @param timer The expired timer. */ void state_w4ack(t_line_timer timer); /** * Process timeout in @ref DS_W4ACK_RE_INVITE state. * @param timer The expired timer. */ void state_w4ack_re_invite(t_line_timer timer); /** * Process timeout in @ref DS_W4RE_INVITE_RESP state. * @param timer The expired timer. */ void state_w4re_invite_resp(t_line_timer timer); /** * Process timeout in @ref DS_W4ANSWER state. * @param timer The expired timer. */ void state_w4answer(t_line_timer timer); /** * Process timeout in @ref DS_CONFIRMED state. * @param timer The expired timer. */ void state_confirmed(t_line_timer timer); //@} /** Make the re-INVITE session the current session. */ void activate_new_session(void); /** * Process SDP answer in 1xx and 2xx responses if present. * Apply ringing tone for a 180 response. * Determine if call should be canceled due to unsupported * or missing SDP. * @param r The 1XX/2XX response. */ void process_1xx_2xx_invite_resp(t_response *r); /** * Acknowledge a reveived 2xx response on an INVITE. * @param r The 2XX response. */ void ack_2xx_invite(t_response *r); /** * Send PRACK if the response requires it. * @param r The response. */ void send_prack_if_required(t_response *r); /** * Determine if a reliable provisional repsonse must be discarded. * A provisional response must be discarded because it is a retransmission * or received out of order. * Initializes the remote response nr if the response is the * first response. * @param r The provisional response. * @return true, discard response. * @return false, otherwise */ bool must_discard_100rel(t_response *r); /** * Respond to an incoming PRACK. * @param r The incoming PRACK. * @param tuid Transaction user id * @param tid Transaction id * @return true, if a success response was given. * @return false if an error response was given. */ bool respond_prack(t_request *r, t_tuid tuid, t_tid tid); virtual void send_request(t_request *r, t_tuid tuid); public: /** @name Timer durations and timer id's */ //@{ unsigned long dur_ack_timeout; /**< @ref LTMR_ACK_TIMEOUT duration (ms) */ t_object_id id_ack_timeout; /**< @ref LTMR_ACK_TIMEOUT timer id */ t_object_id id_ack_guard; /**< @ref LTMR_ACK_GUARD timer id */ t_object_id id_re_invite_guard; /**< @ref LTMR_RE_INVITE_GUARD timer id */ t_object_id id_glare_retry; /**< @ref LTMR_GLARE_RETRY timer id */ t_object_id id_cancel_guard; /**< @ref LTMR_CANCEL_GUARD timer id */ //@} /** @name RFC 3262 100rel timers */ //@{ unsigned long dur_100rel_timeout; /**< @ref LTMR_100REL_TIMEOUT duration (ms) */ t_object_id id_100rel_timeout; /**< @ref LTMR_100REL_TIMEOUT timer id */ t_object_id id_100rel_guard; /**< @ref LTMR_100REL_GUARD timer id */ //@} /** Indicates if last incoming REFER was accepted. */ bool refer_accepted; /** Indicates if the call transfer triggered by the last outgoing REFER succeeded. */ bool refer_succeeded; /** Indicates if the last outgoing REFER request failed. */ bool out_refer_req_failed; /** * Indicates if this dialog is setup because the user told to do * so by a REFER. */ bool is_referred_call; /** State of an outgoing REFER. */ t_refer_state refer_state; /** * Constructor. * @param _line The line owning this dialog. */ t_dialog(t_line *_line); /** Destructor. */ virtual ~t_dialog(); virtual t_request *create_request(t_method m); virtual t_dialog *copy(void); /** * Send INIVTE request. * @param to_uri The URI to be used a request-URI and To header URI * @param to_display Display name for To header. * @param subject If not empty, this string will go into the Subject header. * @param hdr_referred_by The Reffered-By header to be put in the INVITE. * @param hdr_replaces The Replaces header to be put in the INVITE. * @param hdr_require Required extensions to be put in the Require header. * @param hdr_request_disposition Request-Disposition header to be put in the INVITE. * @param anonymous Inidicates if the INVITE should be sent anonymous. * * @pre Dialog is in @ref DS_NULL state. */ void send_invite(const t_url &to_uri, const string &to_display, const string &subject, const t_hdr_referred_by &hdr_referred_by, const t_hdr_replaces &hdr_replaces, const t_hdr_require &hdr_require, const t_hdr_request_disposition &hdr_request_disposition, bool anonymous); /** * Resend the INVITE with an authorization header containing credentials * for the challenge in the response. * @param resp, the response on the INVITE. * @return true, if resending succeeded. * @return false, if credentials could not be determined. * * @pre The response must be a 401 or 407. */ bool resend_invite_auth(t_response *resp); /** * Resend the INVITE because the far-end did not support all required * extensions. Extensions that could not be supported will not be * required this time. * @param resp The response in the INVITE. * @return true, if resending succeeded. * @return false, if far-end did not indicate which extensions are * unsupported. Or if a required extension could not be disabled. * * @pre The response must be a 420. */ bool resend_invite_unsupported(t_response *resp); /** * Redirect INVITE to the next destination. * @param resp The response on the INVITE. * @return true, if INIVTE was redirected successfully. * @return false, if there is no next destination. * * @pre The response must be a 3XX. */ bool redirect_invite(t_response *resp); /** * Failover INVITE to the next destination from DNS lookup. * @return true, if INIVTE was successfully sent. * @return false, if there is no next destination. */ bool failover_invite(void); /** Send BYE request. */ void send_bye(void); /** Send OPTIONS request. */ void send_options(void); /** * Send CANCEL request. * If an early dialog exists, then the CANCEL can be sent * right away as a response has been received for the INVITE. * Otherwise, the CANCEL will be sent as soon as an early dialog * is created. * @param early_dialog_exists Indicates if an early dialog exists. */ void send_cancel(bool early_dialog_exists); /** * Indicate that the dialog must be ended if a 2XX is received * on an INVITE. * @param on Set/clear indication. */ void set_end_after_2xx_invite(bool on); /** * Send re-INVITE. * @pre session_re_invite attribute contains the session * information for the re-INVITE. */ void send_re_invite(void); virtual bool resend_request_auth(t_response *resp); virtual bool redirect_request(t_response *resp); virtual bool failover_request(t_response *resp); /** * Hold call. * If rtp_only is false, then a re-INVITE will be sent. * @param rtponly Indicates if only the RTP streams should be stopped and * the soundcard freed without any SIP signaling. */ void hold(bool rtponly = false); /** Retrieve call (send re-INVITE if needed). */ void retrieve(void); /** Kill all RTP stream associated with this dialog. */ void kill_rtp(void); /** * Refer a call (send REFER). * @param uri URI of the refer target. * @param display Display name of the refer target. */ void send_refer(const t_url &uri, const string &display); /** * Send DTMF digit. * @param digit The digit. * @param inband Indicates if digit must be sent inband. * @param info Indicates if digit must be sent in a SIP INFO. * * @pre Either inband or info or none of the indicators is true. * @post If none of the indicators is true, then RFC 2833 is used. */ void send_dtmf(char digit, bool inband, bool info); /** * Create a binding for the media port via STUN. * @return true, if binding is created. * @return false, if binding cannot be created. */ bool stun_bind_media(void); /** @name Handle received events */ //@{ void recvd_response(t_response *r, t_tuid tuid, t_tid tid); void recvd_request(t_request *r, t_tuid tuid, t_tid tid); /** * Handle incoming CANCEL request. * @param r The CANCEL request. * @param cancel_tid Transaction id of the CANCEL transaction. * @param target_tid Transaction id of the transaction to be cancellerd. */ void recvd_cancel(t_request *r, t_tid cancel_tid, t_tid target_tid); /** * Handle incoming STUN response. * @param r The STUN response. * @param tuid Transaction user id * @param tid Transaction id */ void recvd_stun_resp(StunMessage *r, t_tuid tuid, t_tid tid); /** * Handle the response from the user on the question for refer * permission. This response is received on the dialog that received * the REFER before. * @param permission Permission response from the user. * @param r The REFER request that was received. */ void recvd_refer_permission(bool permission, t_request *r); //@} /** Answer a call (send 200 OK). */ void answer(void); /** * Reject a call. * @param code The response code to reject the call with. * @param reason A specific reason may be given to the error code. If no * reason is specified the default reason is used. * * @pre code >= 400 */ void reject(int code, string reason = ""); /** * Redirect a call. * @param code The response code to redirect the call with. * @param A specific reason may be give to the error code. If no * reason is specified the default reason is used. * @param destinations The list of redirect destinations in order of * preference. * * @pre code is 3XX. */ void redirect(const list &destinations, int code, string reason = ""); virtual bool match_response(t_response *r, t_tuid tuid); /** * Match STUN response with dialog. * @param r STUN response. * @param tuid Transaction user id * @return true, if response matches. * @return false, otherwise. */ bool match_response(StunMessage *r, t_tuid tuid); /** * Match CANCEL request with dialog. * @param r CANCEL request. * @param target_tid Transaction id of transaction to be cancelled. * @return true, if request matches. * @return false, otherwise. */ bool match_cancel(t_request *r, t_tid target_tid); /** * Check if an incoming INVITE is a retransmission. * @param r The INVITE request. * @return true, if INVITE is a retransmission. * @return false, otherwise. */ bool is_invite_retrans(t_request *r); /** Process a retransmission of an incoming INVITE. */ void process_invite_retrans(void); /** * Get the state of the dialog. * @return The dialog state. */ t_dialog_state get_state(void) const; /** * Process dialog timer timeout. * @param timer The timer that expired. */ void timeout(t_line_timer timer); /** * Process subcribe timer timeout (REFER subscription). * @param timer The timer that expired. * @param event_type Event type of the subscription. * @param event_id Event id of the subscription. */ void timeout_sub(t_subscribe_timer timer, const string &event_type, const string &event_id); /** * Get the phone that belongs to this dialog. * @return The phone object. */ t_phone *get_phone(void) const; /** * Get the line that belongs to this dialog. * @return The line object. */ t_line *get_line(void) const; /** * Get the session belonging to this dialog. * @return The session belonging to this dialog. * @return NULL if there is no session. */ t_session * get_session(void) const; /** * Get the audio session belonging to this dialog. * @return The audio session belonging to this dialog. * @return NULL if there is no audio session. */ t_audio_session *get_audio_session(void) const; /** * Check if the dialog has an acitve session. * @return true, if the dialog has an active session. * @return false, otherwise. */ bool has_active_session(void) const; /** * Notify the dialog of the progress of a reference. * @param r The response sent by the refer target. */ void notify_refer_progress(t_response *r); /** * Check if a dialog will be released. * @return true, if the dialog will be released. * @return false, otherwise. */ bool will_release(void) const; }; #endif twinkle-1.10.1/src/diamondcard.cpp000066400000000000000000000070071277565361200170310ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "diamondcard.h" #include #include "twinkle_config.h" #include "util.h" #include "sockets/url.h" #define DIAMONDCARD_DISTSITE "twinkle" #define DIAMONDCARD_URL_SIGNUP "https://www.diamondcard.us/exec/voip-login?act=sgn&spo=%DISTSITE" #define DIAMONDCARD_URL_ACTION "https://www.diamondcard.us/exec/voip-login?"\ "accId=%ACCID&pinCode=%PIN&act=%ACT&spo=%DISTSITE" string diamondcard_url(t_dc_action action, const string &accountId, const string &pinCode) { string url; if (action == DC_ACT_SIGNUP) { url = DIAMONDCARD_URL_SIGNUP; } else { url = DIAMONDCARD_URL_ACTION; url = replace_first(url, "%ACCID", t_url::escape_hnv(accountId)); url = replace_first(url, "%PIN", t_url::escape_hnv(pinCode)); switch (action) { case DC_ACT_BALANCE_HISTORY: url = replace_first(url, "%ACT", "bh"); break; case DC_ACT_RECHARGE: url = replace_first(url, "%ACT", "rch"); break; case DC_ACT_CALL_HISTORY: url = replace_first(url, "%ACT", "ch"); break; case DC_ACT_ADMIN_CENTER: url = replace_first(url, "%ACT", "log"); break; default: assert(false); } } url = replace_first(url, "%DISTSITE", DIAMONDCARD_DISTSITE); return url; } void diamondcard_set_user_config(t_user &user, const string &displayName, const string &accountId, const string &pinCode) { // User user.set_display(displayName); user.set_name(accountId); // The real domain name is "diamondcard.us", but Diamondcard // instructs users to use "sip.diamondcard.us" for the domain. // This latter name is resolvable by both a DNS SRV and A lookup. // So clients not capable of DNS SRV lookups van still work with // this domain name. To be consistent with other client settings // Twinkle uses this domain name too. user.set_domain("sip.diamondcard.us"); user.set_auth_name(accountId); user.set_auth_pass(pinCode); // SIP server user.set_use_outbound_proxy(true); user.set_outbound_proxy(t_url("sip:sip.diamondcard.us")); // Audio codecs list codecs; codecs.push_back(CODEC_G711_ULAW); codecs.push_back(CODEC_G711_ALAW); #ifdef HAVE_ILBC codecs.push_back(CODEC_ILBC); #endif codecs.push_back(CODEC_GSM); user.set_codecs(codecs); // Voice mail user.set_mwi_vm_address("80"); // IM user.set_im_send_iscomposing(false); // Presence user.set_pres_publish_startup(false); // NAT user.set_enable_nat_keepalive(true); user.set_timer_nat_keepalive(20); // Address format user.set_numerical_user_is_phone(true); } listdiamondcard_get_users(t_phone *phone) { list users = phone->ref_users(); list diamond_users; for (list::const_iterator it = users.begin(); it != users.end(); ++it) { if ((*it)->is_diamondcard_account()) { diamond_users.push_back(*it); } } return diamond_users; } twinkle-1.10.1/src/diamondcard.h000066400000000000000000000041441277565361200164750ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** @file * Diamondcard settings (www.diamondcard.us) */ #ifndef _DIAMONDCARD_H #define _DIAMONDCARD_H #include #include #include "user.h" #include "phone.h" #define DIAMONDCARD_DOMAIN "diamondcard.us" using namespace std; /** Actions that can be performed on the Diamondcard web site */ enum t_dc_action { DC_ACT_SIGNUP, DC_ACT_BALANCE_HISTORY, DC_ACT_RECHARGE, DC_ACT_CALL_HISTORY, DC_ACT_ADMIN_CENTER }; /** * Get the URL of a Diamondcard web page for an action. * @param action [in] Action for which the URL is requested. * @param displayName [in] The display name of the user. * @param accountId [in] Account ID of the user. N/A for signup. * @param pinCode [in] PIN code of the user. N/A for signup. * @return URL of the web page for the requested action. */ string diamondcard_url(t_dc_action action, const string &accountId, const string &pinCode); /** * Configure a user profile for a Diamondcard account. * @param user [inout] The user profile to configure. * @param accountId [in] Account ID of the user. * @param pinCode [in] PIN code of the user. */ void diamondcard_set_user_config(t_user &user, const string &displayName, const string &accountId, const string &pinCode); /** * Get all active Diamondcard users. * @return List of active Diamondcard users. */ listdiamondcard_get_users(t_phone *phone); #endif twinkle-1.10.1/src/epa.cpp000066400000000000000000000312311277565361200153250ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "epa.h" #include "log.h" #include "phone.h" #include "timekeeper.h" #include "util.h" #include "audits/memman.h" extern t_phone *phone; extern t_event_queue *evq_timekeeper; extern string local_hostname; ///////////// // PRIVATE ///////////// void t_epa::enqueue_request(t_request *r) { log_file->write_header("t_epa::enqueue_request", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Enqueue request\n"); log_publication(); log_file->write_footer(); queue_publish.push(r); } ///////////// // PROTECTED ///////////// void t_epa::log_publication() const { log_file->write_raw("Event: "); log_file->write_raw(event_type); log_file->write_raw(", URI: "); log_file->write_raw(request_uri.encode()); log_file->write_raw(", SIP-ETag: "); log_file->write_raw(etag); log_file->write_endl(); } void t_epa::remove_client_request(t_client_request **cr) { if ((*cr)->dec_ref_count() == 0) { MEMMAN_DELETE(*cr); delete *cr; } *cr = NULL; } t_request *t_epa::create_publish(unsigned long expires, t_sip_body *body) const { t_user *user_config = phone_user->get_user_profile(); t_request *r = phone_user->create_request(PUBLISH, request_uri); // Call-ID r->hdr_call_id.set_call_id(NEW_CALL_ID(user_config)); // CSeq r->hdr_cseq.set_method(PUBLISH); r->hdr_cseq.set_seqnr(NEW_SEQNR); // To r->hdr_to.set_uri(user_config->create_user_uri(false)); r->hdr_to.set_display(user_config->get_display(false)); // RFC 3903 4 Expires r->hdr_expires.set_time(expires); // RFC 3903 4 Event r->hdr_event.set_event_type(event_type); // SIP-If-Match if (!etag.empty()) { r->hdr_sip_if_match.set_etag(etag); } // Body if (body) { r->body = body; r->hdr_content_type.set_media(body->get_media()); } return r; } void t_epa::send_request(t_request *r, t_tuid tuid) const { phone->send_request(phone_user->get_user_profile(), r, tuid); } void t_epa::send_publish_from_queue(void) { // If there is a PUBLISH in the queue, then send it while (!queue_publish.empty()) { log_file->write_header("t_epa::send_publish_from_queue", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Get PUBLISH from queue.\n"); log_publication(); log_file->write_footer(); t_request *req = queue_publish.front(); queue_publish.pop(); // Update the SIP-If-Match header to the current entity tag if (!etag.empty()) { req->hdr_sip_if_match.set_etag(etag); } else { req->hdr_sip_if_match.clear(); } if (req->hdr_expires.time == 0) { if (epa_state != EPA_PUBLISHED) { log_file->write_header("t_epa::send_publish_from_queue", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Nothing published, discard unpublish\n"); log_publication(); log_file->write_footer(); MEMMAN_DELETE(req); delete req; continue; } is_unpublishing = true; } else { is_unpublishing = false; } stop_timer(PUBLISH_TMR_PUBLICATION); req_out = new t_client_request(phone_user->get_user_profile(), req, 0); MEMMAN_NEW(req_out); send_request(req, req_out->get_tuid()); MEMMAN_DELETE(req); delete req; break; } } void t_epa::start_timer(t_publish_timer timer, long duration) { t_tmr_publish *t = NULL; switch(timer) { case PUBLISH_TMR_PUBLICATION: t = new t_tmr_publish(duration, timer, event_type); MEMMAN_NEW(t); id_publication_timeout = t->get_object_id(); break; default: assert(false); } evq_timekeeper->push_start_timer(t); MEMMAN_DELETE(t); delete t; } void t_epa::stop_timer(t_publish_timer timer) { unsigned short *id; switch(timer) { case PUBLISH_TMR_PUBLICATION: id = &id_publication_timeout; break; default: assert(false); } if (*id != 0) evq_timekeeper->push_stop_timer(*id); *id = 0; } ////////// // PUBLIC ////////// t_epa::t_epa(t_phone_user *pu, const string &_event_type, const t_url _request_uri) : phone_user(pu), epa_state(EPA_UNPUBLISHED), event_type(_event_type), request_uri(_request_uri), id_publication_timeout(0), publication_expiry(3600), default_duration(3600), is_unpublishing(false), cached_body(NULL), req_out(NULL) {} t_epa::~t_epa() { clear(); } t_epa::t_epa_state t_epa::get_epa_state(void) const { return epa_state; } string t_epa::get_failure_msg(void) const { return failure_msg; } t_phone_user *t_epa::get_phone_user(void) const { return phone_user; } t_user *t_epa::get_user_profile(void) const { return phone_user->get_user_profile(); } bool t_epa::recv_response(t_response *r, t_tuid tuid, t_tid tid) { // Discard response if it does not match a pending request if (!req_out) return true; t_request *req = req_out->get_request(); if (r->hdr_cseq.method != req->method) return true; // Ignore provisional responses if (r->is_provisional()) return true; if (r->is_success()) { // RFC 3903 11.3 // A 2XX response must contain a SIP-ETag header if (r->hdr_sip_etag.is_populated()) { etag = r->hdr_sip_etag.etag; } else { log_file->write_report("SIP-ETag header missing from PUBLISH 2XX response.", "t_epa::recv_response", LOG_NORMAL, LOG_WARNING); etag.clear(); } // RFC 3903 1.1.1 says that the Expires header is mandatory // in a 2XX response. Some SIP servers do not include this // however. To interoperate with such servers, assume that // the granted expiry time equals the requested expiry time. if (!r->hdr_expires.is_populated()) { r->hdr_expires.set_time( req->hdr_expires.time); log_file->write_header("t_epa::recv_response", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Mandatory Expires header missing.\n"); log_file->write_raw("Assuming expires = "); log_file->write_raw(r->hdr_expires.time); log_file->write_endl(); log_publication(); log_file->write_footer(); } // If some faulty server sends a non-zero expiry time in // a response on an unsubscribe request, then ignore // the expiry time. if (r->hdr_expires.time == 0 || is_unpublishing) { // Unpublish succeeded. stop_timer(PUBLISH_TMR_PUBLICATION); etag.clear(); is_unpublishing = false; epa_state = EPA_UNPUBLISHED; log_file->write_header("t_epa::recv_response", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Unpublish successful.\n"); log_publication(); log_file->write_footer(); } else { log_file->write_header("t_epa::recv_response"); log_file->write_raw("Publication sucessful.\n"); log_publication(); log_file->write_footer(); // Start/refresh publish timer stop_timer(PUBLISH_TMR_PUBLICATION); unsigned long dur = r->hdr_expires.time; dur -= dur / 10; start_timer(PUBLISH_TMR_PUBLICATION, dur * 1000); epa_state = EPA_PUBLISHED; } remove_client_request(&req_out); send_publish_from_queue(); return true; } // Authentication if (r->must_authenticate()) { if (phone_user->authorize(req, r)) { phone_user->resend_request(req, req_out); return true; } // Authentication failed // Handle the 401/407 as a normal failure response } // PUBLISH failed if (is_unpublishing) { // Unpublish failed. // There is nothing we can do about that. Just clear // the internal publication. stop_timer(PUBLISH_TMR_PUBLICATION); etag.clear(); is_unpublishing = false; epa_state = EPA_UNPUBLISHED; log_file->write_header("t_epa::recv_response", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Unpublish failed.\n"); log_publication(); log_file->write_footer(); remove_client_request(&req_out); return true; } if (r->code == R_423_INTERVAL_TOO_BRIEF) { if (!r->hdr_min_expires.is_populated()) { // Violation of RFC 3261 10.3 item 7 log_file->write_report("Min-Expires header missing from 423 response.", "t_epa::recv_response", LOG_NORMAL, LOG_WARNING); } else if (r->hdr_min_expires.time <= publication_expiry) { // Wrong Min-Expires time string s = "Min-Expires ("; s += ulong2str(r->hdr_min_expires.time); s += ") is smaller than the requested "; s += "time ("; s += ulong2str(publication_expiry); s += ")"; log_file->write_report(s, "t_epa::recv_response", LOG_NORMAL, LOG_WARNING); } else { // Publish with the advised interval remove_client_request(&req_out); if (etag.empty()) { // Initial publication. publish(r->hdr_min_expires.time, cached_body); } else { publication_expiry = r->hdr_min_expires.time; refresh_publication(); } return true; } } else if (r->code == R_412_CONDITIONAL_REQUEST_FAILED) { log_file->write_header("t_epa::recv_response"); log_file->write_raw("SIP-ETag mismatch, retry with initial publication.\n"); log_publication(); log_file->write_endl(); log_file->write_footer(); // The state seems to be gone from the presence agent. Clear // the internal pubication state. remove_client_request(&req_out); etag.clear(); epa_state = EPA_UNPUBLISHED; // Retry to publish state publish(publication_expiry, cached_body); return true; } remove_client_request(&req_out); epa_state = EPA_FAILED; failure_msg = int2str(r->code); failure_msg += ' '; failure_msg += r->reason; log_file->write_header("t_epa::recv_response", LOG_NORMAL, LOG_WARNING); log_file->write_raw("PUBLISH failure response.\n"); log_file->write_raw(r->code); log_file->write_raw(" " + r->reason + "\n"); log_publication(); log_file->write_footer(); send_publish_from_queue(); return true; } bool t_epa::match_response(t_response *r, t_tuid tuid) const { return (req_out && req_out->get_tuid() == tuid); } bool t_epa::timeout(t_publish_timer timer) { switch (timer) { case PUBLISH_TMR_PUBLICATION: id_publication_timeout = 0; log_file->write_header("t_epa::timeout"); log_file->write_raw("Publication timed out.\n"); log_publication(); log_file->write_footer(); refresh_publication(); return true; default: assert(false); } return false; } bool t_epa::match_timer(t_publish_timer timer, t_object_id id_timer) const { return id_timer == id_publication_timeout; } void t_epa::publish(unsigned long expires, t_sip_body *body) { t_request *r = create_publish(expires, body); if (req_out) { // A PUBLISH request is pending, queue this one. // Only 1 PUBLISH at a time may be sent. // RFC 3903 4 enqueue_request(r); return; } // If the body equals the cached body, then do not // delete the cached_body as that will delete the body! if (cached_body && body && body != cached_body) { MEMMAN_DELETE(cached_body); delete cached_body; cached_body = NULL; } if (body) { cached_body = body->copy(); } if (expires > 0) { publication_expiry = expires; } else { publication_expiry = default_duration; } is_unpublishing = false; stop_timer(PUBLISH_TMR_PUBLICATION); req_out = new t_client_request(phone_user->get_user_profile(), r, 0); MEMMAN_NEW(req_out); send_request(r, req_out->get_tuid()); MEMMAN_DELETE(r); delete r; } void t_epa::unpublish(void) { if (!req_out && epa_state != EPA_PUBLISHED) { log_file->write_header("t_epa::unpublish", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Nothing published, discard unpublish\n"); log_publication(); log_file->write_footer(); return; } t_request *r = create_publish(0, NULL); if (req_out) { // A PUBLISH request is pending, queue this one. // Only 1 PUBLISH at a time may be sent. // RFC 3903 4 enqueue_request(r); return; } if (cached_body) { MEMMAN_DELETE(cached_body); delete cached_body; cached_body = NULL; } is_unpublishing = true; stop_timer(PUBLISH_TMR_PUBLICATION); req_out = new t_client_request(phone_user->get_user_profile(), r, 0); MEMMAN_NEW(req_out); send_request(r, req_out->get_tuid()); MEMMAN_DELETE(r); delete r; } void t_epa::refresh_publication(void) { publish(publication_expiry, NULL); } void t_epa::clear(void) { if (req_out) remove_client_request(&req_out); if (id_publication_timeout) stop_timer(PUBLISH_TMR_PUBLICATION); if (cached_body) { MEMMAN_DELETE(cached_body); delete cached_body; cached_body = NULL; } // Cleanup list of unsent PUBLISH messages while (!queue_publish.empty()) { t_request *r = queue_publish.front(); queue_publish.pop(); MEMMAN_DELETE(r); delete r; } } twinkle-1.10.1/src/epa.h000066400000000000000000000130631277565361200147750ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Event Publication Agent (EPA) [RFC 3903] */ #ifndef _EPA_H #define _EPA_H #include #include #include "id_object.h" #include "phone_user.h" #include "sockets/url.h" #include "parser/sip_body.h" #include "protocol.h" using namespace std; /** Event Publication Agent (EPA) [RFC 3903] */ class t_epa { public: /** State of the EPA */ enum t_epa_state { EPA_UNPUBLISHED, /**< The event has not been published. */ EPA_PUBLISHED, /**< The event has been published. */ EPA_FAILED, /**< Failed to publish the event. */ }; private: /** * Queue of pending outgoing PUBLISH requests. A next PUBLISH * will only be sent after the previous PUBLISH has been * answered. */ queue queue_publish; /** * Enqueue a request. * @param r [in] Request to enqueue. */ void enqueue_request(t_request *r); protected: /** Phone user for whom publications are issued. */ t_phone_user *phone_user; /** EPA state. */ t_epa_state epa_state; /** Detailed failure message when @ref epa_state == @ref EPA_FAILED */ string failure_msg; /** * Entity tag associated with the publication. * For an initial publication there is no entity tag yet. */ string etag; /** Event for which the event state is published. */ string event_type; /** Request-URI for the publish request. */ t_url request_uri; /** Timer indicating when a publication must be refreshed. */ t_object_id id_publication_timeout; /** Expiry duration (sec) of a publication. */ unsigned long publication_expiry; /** Default duration for a publication/ */ unsigned long default_duration; /** Indicates if an unpublish is in progress. */ bool is_unpublishing; /** Cached body of last publication. */ t_sip_body *cached_body; /** Log the publication details */ void log_publication(void) const; /** * Remove a pending request. Pass one of the client request pointers. * @param cr [in] Client request to remove. */ void remove_client_request(t_client_request **cr); /** * Create a PUBLISH request. * @param expires [in] Expiry time in seconds. * @param body [in] Body for the request. The body will be destroyed when * the request will be destroyed. */ virtual t_request *create_publish(unsigned long expires, t_sip_body *body) const; /** * Send request. * @param r [in] Request to send. * @param tuid [in] Transaction user id. */ void send_request(t_request *r, t_tuid tuid) const; /** * Send the next PUBLISH request from the queue. * If the queue is empty, then this method does nothing. */ void send_publish_from_queue(void); /** * Start a publication timer. * @param timer [in] Type of publication timer. * @param duration [in] Duration of timer in ms */ void start_timer(t_publish_timer timer, long duration); /** * Stop a publication timer. * @param timer [in] Type of publication timer. */ void stop_timer(t_publish_timer timer); public: /** Pending request */ t_client_request *req_out; /** Constructor. */ t_epa(t_phone_user *pu, const string &_event_type, const t_url _request_uri); /** Destructor. */ virtual ~t_epa(); /** @name Getters */ //@{ t_epa_state get_epa_state(void) const; string get_failure_msg(void) const; t_phone_user *get_phone_user(void) const; //@} /** * Get the user profile of the user. * @return The user profile. */ t_user *get_user_profile(void) const; /** * Receive PUBLISH response. * @param r [in] Received response. * @param tuid [in] Transaction user id. * @param tid [in] Transaction id. * @return The return value indicates if processing is finished. */ virtual bool recv_response(t_response *r, t_tuid tuid, t_tid tid); /** * Match response with a pending publish. * @param r [in] The response. * @param tuid [in] Transaction user id. * @return True if the response matches, otherwise false. */ virtual bool match_response(t_response *r, t_tuid tuid) const; /** * Process timeouts * @param timer [in] Type of publication timer. * @return The return value indicates if processing is finished. */ virtual bool timeout(t_publish_timer timer); /** * Match timer id with a running timer. * @param timer [in] Type of publication timer. * @return True, if id matches, otherwise false. */ virtual bool match_timer(t_publish_timer timer, t_object_id id_timer) const; /** * Publish event state. * @param expired [in] Duration of publication in seconds. * @param body [in] Body for PUBLISH request. * @note The body will be deleted when the PUBLISH has been sent. * The caller of this method should *not* delete the body. */ virtual void publish(unsigned long expires, t_sip_body *body); /** Terminate publication. */ virtual void unpublish(void); /** Refresh publication. */ virtual void refresh_publication(void); /** Clear all state */ virtual void clear(void); }; #endif twinkle-1.10.1/src/events.cpp000066400000000000000000000431541277565361200160730ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include "events.h" #include "log.h" #include "userintf.h" #include "util.h" #include "audits/memman.h" string event_type2str(t_event_type t) { switch(t) { case EV_QUIT: return "EV_QUIT"; case EV_NETWORK: return "EV_NETWORK"; case EV_USER: return "EV_USER"; case EV_TIMEOUT: return "EV_TIMEOUT"; case EV_FAILURE: return "EV_FAILURE"; case EV_START_TIMER: return "EV_START_TIMER"; case EV_STOP_TIMER: return "EV_STOP_TIMER"; case EV_ABORT_TRANS: return "EV_ABORT_TRANS"; case EV_STUN_REQUEST: return "EV_STUN_REQUEST"; case EV_STUN_RESPONSE: return "EV_STUN_RESPONSE"; case EV_NAT_KEEPALIVE: return "EV_NAT_KEEPALIVE"; case EV_ICMP: return "EV_ICMP"; case EV_UI: return "EV_UI"; case EV_ASYNC_RESPONSE: return "EV_ASYNC_RESPONSE"; case EV_BROKEN_CONNECTION: return "EV_BROKEN_CONNECTION"; case EV_TCP_PING: return "EV_TCP_PING"; case EV_FN_CALL: return "EV_FN_CALL"; } return "UNKNOWN"; } /////////////////////////////////////////////////////////// // class t_event_network /////////////////////////////////////////////////////////// t_event_network::t_event_network(t_sip_message *m) : t_event() { msg = m->copy(); src_addr = 0; src_port = 0; dst_addr = 0; dst_port = 0; transport.clear(); } t_event_network::~t_event_network() { MEMMAN_DELETE(msg); delete msg; } t_event_type t_event_network::get_type(void) const { return EV_NETWORK; } t_sip_message *t_event_network::get_msg(void) const { return msg; } /////////////////////////////////////////////////////////// // class t_event_quit /////////////////////////////////////////////////////////// t_event_quit::~t_event_quit() {} t_event_type t_event_quit::get_type(void) const { return EV_QUIT; } /////////////////////////////////////////////////////////// // class t_event_user /////////////////////////////////////////////////////////// t_event_user::t_event_user(t_user *u, t_sip_message *m, unsigned short _tuid, unsigned short _tid) : t_event() { msg = m->copy(); tuid = _tuid; tid = _tid; tid_cancel_target = 0; if (u) { user_config = u->copy(); } else { user_config = NULL; } } t_event_user::t_event_user(t_user *u, t_sip_message *m, unsigned short _tuid, unsigned short _tid, unsigned short _tid_cancel_target) : t_event() { msg = m->copy(); tuid = _tuid; tid = _tid; tid_cancel_target = _tid_cancel_target; if (u) { user_config = u->copy(); } else { user_config = NULL; } } t_event_user::~t_event_user() { MEMMAN_DELETE(msg); delete msg; if (user_config) { MEMMAN_DELETE(user_config); delete user_config; } } t_event_type t_event_user::get_type(void) const { return EV_USER; } t_sip_message *t_event_user::get_msg(void) const { return msg; } unsigned short t_event_user::get_tuid(void) const { return tuid; } unsigned short t_event_user::get_tid(void) const { return tid; } unsigned short t_event_user::get_tid_cancel_target(void) const { return tid_cancel_target; } t_user *t_event_user::get_user_config(void) const { return user_config; } /////////////////////////////////////////////////////////// // class t_event_timeout /////////////////////////////////////////////////////////// t_event_timeout::t_event_timeout(t_timer *t) : t_event() { timer = t->copy(); } t_event_timeout::~t_event_timeout() { MEMMAN_DELETE(timer); delete timer; } t_event_type t_event_timeout::get_type(void) const { return EV_TIMEOUT; } t_timer *t_event_timeout::get_timer(void) const { return timer; } /////////////////////////////////////////////////////////// // class t_event_failure /////////////////////////////////////////////////////////// t_event_failure::t_event_failure(t_failure f, unsigned short _tid) : t_event(), failure(f), tid_populated(true), tid(_tid) {} t_event_failure::t_event_failure(t_failure f, const string &_branch, const t_method &_cseq_method) : failure(f), tid_populated(false), branch(_branch), cseq_method(_cseq_method) {} t_event_type t_event_failure::get_type(void) const { return EV_FAILURE; } t_failure t_event_failure::get_failure(void) const { return failure; } unsigned short t_event_failure::get_tid(void) const { return tid; } string t_event_failure::get_branch(void) const { return branch; } t_method t_event_failure::get_cseq_method(void) const { return cseq_method; } bool t_event_failure::is_tid_populated(void) const { return tid_populated; } /////////////////////////////////////////////////////////// // class t_event_start_timer /////////////////////////////////////////////////////////// t_event_start_timer::t_event_start_timer(t_timer *t) : t_event() { timer = t->copy(); } t_event_type t_event_start_timer::get_type(void) const { return EV_START_TIMER; } t_timer *t_event_start_timer::get_timer(void) const { return timer; } /////////////////////////////////////////////////////////// // class t_event_stop_timer /////////////////////////////////////////////////////////// t_event_stop_timer::t_event_stop_timer(unsigned short id) : t_event() { timer_id = id; } t_event_type t_event_stop_timer::get_type(void) const { return EV_STOP_TIMER; } unsigned short t_event_stop_timer::get_timer_id(void) const { return timer_id; } /////////////////////////////////////////////////////////// // class t_event_abort_trans /////////////////////////////////////////////////////////// t_event_abort_trans::t_event_abort_trans(unsigned short _tid) : t_event() { tid = _tid; } t_event_type t_event_abort_trans::get_type(void) const { return EV_ABORT_TRANS; } unsigned short t_event_abort_trans::get_tid(void) const { return tid; } /////////////////////////////////////////////////////////// // class t_event_stun_request /////////////////////////////////////////////////////////// t_event_stun_request::t_event_stun_request(t_user *u, StunMessage *m, t_stun_event_type ev_type, unsigned short _tuid, unsigned short _tid) : t_event() { msg = new StunMessage(*m); MEMMAN_NEW(msg); stun_event_type = ev_type; tuid = _tuid; tid = _tid; dst_addr = 0; dst_port = 0; user_config = u->copy(); } t_event_stun_request::~t_event_stun_request() { MEMMAN_DELETE(msg); delete msg; MEMMAN_DELETE(user_config); delete user_config; } t_event_type t_event_stun_request::get_type(void) const { return EV_STUN_REQUEST; } StunMessage *t_event_stun_request::get_msg(void) const { return msg; } unsigned short t_event_stun_request::get_tuid(void) const { return tuid; } unsigned short t_event_stun_request::get_tid(void) const { return tid; } t_stun_event_type t_event_stun_request::get_stun_event_type(void) const { return stun_event_type; } t_user *t_event_stun_request::get_user_config(void) const { return user_config; } /////////////////////////////////////////////////////////// // class t_event_stun_response /////////////////////////////////////////////////////////// t_event_stun_response::t_event_stun_response(StunMessage *m, unsigned short _tuid, unsigned short _tid) : t_event() { msg = new StunMessage(*m); MEMMAN_NEW(msg); tuid = _tuid; tid = _tid; } t_event_stun_response::~t_event_stun_response() { MEMMAN_DELETE(msg); delete(msg); } t_event_type t_event_stun_response::get_type(void) const { return EV_STUN_RESPONSE; } StunMessage *t_event_stun_response::get_msg(void) const { return msg; } unsigned short t_event_stun_response::get_tuid(void) const { return tuid; } unsigned short t_event_stun_response::get_tid(void) const { return tid; } /////////////////////////////////////////////////////////// // class t_event_nat_keepalive /////////////////////////////////////////////////////////// t_event_type t_event_nat_keepalive::get_type(void) const { return EV_NAT_KEEPALIVE; } /////////////////////////////////////////////////////////// // class t_event_icmp /////////////////////////////////////////////////////////// t_event_icmp::t_event_icmp(const t_icmp_msg &m) : icmp(m) {} t_event_type t_event_icmp::get_type(void) const { return EV_ICMP; } t_icmp_msg t_event_icmp::get_icmp(void) const { return icmp; } /////////////////////////////////////////////////////////// // class t_event_ui /////////////////////////////////////////////////////////// t_event_ui::t_event_ui(t_ui_event_type _type) : t_event(), type(_type) {} t_event_type t_event_ui::get_type(void) const { return EV_UI; } void t_event_ui::set_line(int _line) { line = _line; } void t_event_ui::set_codec(t_audio_codec _codec) { codec = _codec; } void t_event_ui::set_dtmf_event(t_dtmf_ev _dtmf_event) { dtmf_event = _dtmf_event; } void t_event_ui::set_encrypted(bool on) { encrypted = on; } void t_event_ui::set_cipher_mode(const string &_cipher_mode) { cipher_mode = _cipher_mode; } void t_event_ui::set_zrtp_sas(const string &sas) { zrtp_sas = sas; } void t_event_ui::set_display_msg(const string &_msg, t_msg_priority &_msg_priority) { msg = _msg; msg_priority = _msg_priority; } void t_event_ui::exec(t_userintf *user_intf) { switch (type) { case TYPE_UI_CB_DTMF_DETECTED: ui->cb_dtmf_detected(line, dtmf_event); break; case TYPE_UI_CB_SEND_DTMF: ui->cb_send_dtmf(line, dtmf_event); break; case TYPE_UI_CB_RECV_CODEC_CHANGED: ui->cb_recv_codec_changed(line, codec); break; case TYPE_UI_CB_LINE_STATE_CHANGED: ui->cb_line_state_changed(); break; case TYPE_UI_CB_LINE_ENCRYPTED: ui->cb_line_encrypted(line, encrypted, cipher_mode); break; case TYPE_UI_CB_SHOW_ZRTP_SAS: ui->cb_show_zrtp_sas(line, zrtp_sas); break; case TYPE_UI_CB_ZRTP_CONFIRM_GO_CLEAR: ui->cb_zrtp_confirm_go_clear(line); break; case TYPE_UI_CB_QUIT: ui->cmd_quit(); break; default: assert(false); } } /////////////////////////////////////////////////////////// // class t_event_async_response /////////////////////////////////////////////////////////// t_event_async_response::t_event_async_response(t_response_type type) : t_event(), response_type(type) {} t_event_type t_event_async_response::get_type(void) const { return EV_ASYNC_RESPONSE; } void t_event_async_response::set_bool_response(bool b) { bool_response = b; } t_event_async_response::t_response_type t_event_async_response::get_response_type(void) const { return response_type; } bool t_event_async_response::get_bool_response(void) const { return bool_response; } /////////////////////////////////////////////////////////// // class t_event_broken_connection /////////////////////////////////////////////////////////// t_event_broken_connection::t_event_broken_connection(const t_url &url) : t_event(), user_uri_(url) {} t_event_type t_event_broken_connection::get_type(void) const { return EV_BROKEN_CONNECTION; } t_url t_event_broken_connection::get_user_uri(void) const { return user_uri_; } /////////////////////////////////////////////////////////// // class t_event_tcp_ping /////////////////////////////////////////////////////////// t_event_tcp_ping::t_event_tcp_ping(const t_url &url, unsigned int dst_addr, unsigned short dst_port) : t_event(), user_uri_(url), dst_addr_(dst_addr), dst_port_(dst_port) {} t_event_type t_event_tcp_ping::get_type(void) const { return EV_TCP_PING; } t_url t_event_tcp_ping::get_user_uri(void) const { return user_uri_; } unsigned int t_event_tcp_ping::get_dst_addr(void) const { return dst_addr_; } unsigned short t_event_tcp_ping::get_dst_port(void) const { return dst_port_; } /////////////////////////////////////////////////////////// // class t_event_fncall /////////////////////////////////////////////////////////// t_event_fncall::t_event_fncall(std::function fn) : m_fn(fn) { } void t_event_fncall::invoke() { m_fn(); } t_event_type t_event_fncall::get_type(void) const { return EV_FN_CALL; } /////////////////////////////////////////////////////////// // class t_event_queue /////////////////////////////////////////////////////////// t_event_queue::t_event_queue() : sema_evq(0), sema_caught_interrupt(0) {} t_event_queue::~t_event_queue() { log_file->write_header("t_event_queue::~t_event_queue", LOG_NORMAL, LOG_INFO); log_file->write_raw("Clean up event queue.\n"); while (!ev_queue.empty()) { t_event *e = ev_queue.front(); ev_queue.pop(); log_file->write_raw("\nDeleting unprocessed event: \n"); log_file->write_raw("Type: "); log_file->write_raw(event_type2str(e->get_type())); log_file->write_raw(", Pointer: "); log_file->write_raw(ptr2str(e)); log_file->write_endl(); MEMMAN_DELETE(e); delete e; } log_file->write_footer(); } void t_event_queue::push(t_event *e) { mutex_evq.lock(); ev_queue.push(e); mutex_evq.unlock(); sema_evq.up(); } void t_event_queue::push_quit(void) { t_event_quit *event = new t_event_quit(); MEMMAN_NEW(event); push(event); } void t_event_queue::push_fncall(std::function fn) { t_event_fncall* event = new t_event_fncall(fn); MEMMAN_NEW(event); push(event); } void t_event_queue::push_network(t_sip_message *m, const t_ip_port &ip_port) { t_event_network *event = new t_event_network(m); MEMMAN_NEW(event); event->dst_addr = ip_port.ipaddr; event->dst_port = ip_port.port; event->transport = ip_port.transport; push(event); } void t_event_queue::push_user(t_user *user_config, t_sip_message *m, unsigned short tuid, unsigned short tid) { t_event_user *event = new t_event_user(user_config, m, tuid, tid); MEMMAN_NEW(event); push(event); } void t_event_queue::push_user(t_sip_message *m, unsigned short tuid, unsigned short tid) { push_user(NULL, m, tuid, tid); } void t_event_queue::push_user_cancel(t_user *user_config, t_sip_message *m, unsigned short tuid, unsigned short tid, unsigned short target_tid) { t_event_user *event = new t_event_user(user_config, m, tuid, tid, target_tid); MEMMAN_NEW(event); push(event); } void t_event_queue::push_user_cancel(t_sip_message *m, unsigned short tuid, unsigned short tid, unsigned short target_tid) { push_user_cancel(NULL, m, tuid, tid, target_tid); } void t_event_queue::push_timeout(t_timer *t) { t_event_timeout *event = new t_event_timeout(t); MEMMAN_NEW(event); push(event); } void t_event_queue::push_failure(t_failure f, unsigned short tid) { t_event_failure *event = new t_event_failure(f, tid); MEMMAN_NEW(event); push(event); } void t_event_queue::push_failure(t_failure f, const string &branch, const t_method &cseq_method) { t_event_failure *event = new t_event_failure(f, branch, cseq_method); MEMMAN_NEW(event); push(event); } void t_event_queue::push_start_timer(t_timer *t) { t_event_start_timer *event = new t_event_start_timer(t); MEMMAN_NEW(event); push(event); } void t_event_queue::push_stop_timer(unsigned short timer_id) { t_event_stop_timer *event = new t_event_stop_timer(timer_id); MEMMAN_NEW(event); push(event); } void t_event_queue::push_abort_trans(unsigned short tid) { t_event_abort_trans *event = new t_event_abort_trans(tid); MEMMAN_NEW(event); push(event); } void t_event_queue::push_stun_request(t_user *user_config, StunMessage *m, t_stun_event_type ev_type, unsigned short tuid, unsigned short tid, unsigned long ipaddr, unsigned short port, unsigned short src_port) { t_event_stun_request *event = new t_event_stun_request(user_config, m, ev_type, tuid, tid); MEMMAN_NEW(event); event->dst_addr = ipaddr; event->dst_port = port; event->src_port = src_port; push(event); } void t_event_queue::push_stun_response(StunMessage *m, unsigned short tuid, unsigned short tid) { t_event_stun_response *event = new t_event_stun_response(m, tuid, tid); MEMMAN_NEW(event); push(event); } void t_event_queue::push_nat_keepalive(unsigned long ipaddr, unsigned short port) { t_event_nat_keepalive *event = new t_event_nat_keepalive(); MEMMAN_NEW(event); event->dst_addr = ipaddr; event->dst_port = port; push(event); } void t_event_queue::push_icmp(const t_icmp_msg &m) { t_event_icmp *event = new t_event_icmp(m); MEMMAN_NEW(event); push(event); } void t_event_queue::push_refer_permission_response(bool permission) { t_event_async_response *event = new t_event_async_response( t_event_async_response::RESP_REFER_PERMISSION); MEMMAN_NEW(event); event->set_bool_response(permission); push(event); } void t_event_queue::push_broken_connection(const t_url &user_uri) { t_event_broken_connection *event = new t_event_broken_connection(user_uri); MEMMAN_NEW(event); push(event); } void t_event_queue::push_tcp_ping(const t_url &user_uri, unsigned int dst_addr, unsigned short dst_port) { t_event_tcp_ping *event = new t_event_tcp_ping(user_uri, dst_addr, dst_port); MEMMAN_NEW(event); push(event); } t_event *t_event_queue::pop(void) { t_event *e; bool interrupt; do { interrupt = false; sema_evq.down(); mutex_evq.lock(); if (sema_caught_interrupt.try_down()) { // This pop is non-interruptable, so ignore the interrupt interrupt = true; } else { e = ev_queue.front(); ev_queue.pop(); } mutex_evq.unlock(); } while (interrupt); return e; } t_event *t_event_queue::pop(bool &interrupted) { t_event *e; sema_evq.down(); mutex_evq.lock(); if (sema_caught_interrupt.try_down()) { interrupted = true; e = NULL; } else { interrupted = false; e = ev_queue.front(); ev_queue.pop(); } mutex_evq.unlock(); return e; } void t_event_queue::interrupt(void) { sema_caught_interrupt.up(); sema_evq.up(); } twinkle-1.10.1/src/events.h000066400000000000000000000535271277565361200155450ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Threads communicate by passing events via event queues. */ #ifndef _EVENTS_H #define _EVENTS_H #include #include "protocol.h" #include "timekeeper.h" #include "stun/stun.h" #include "audio/audio_codecs.h" #include "audio/rtp_telephone_event.h" #include "parser/sip_message.h" #include "sockets/socket.h" #include "sockets/url.h" #include "threads/mutex.h" #include "threads/sema.h" using namespace std; // Forward declarations class t_userintf; /** Different types of events. */ enum t_event_type { EV_QUIT, /**< Generic quit event */ EV_NETWORK, /**< Network event, eg. SIP message from/to network */ EV_USER, /**< User event, eg. SIP message from/to user */ EV_TIMEOUT, /**< Timer expiry */ EV_FAILURE, /**< Failure, eg. transport failure */ EV_START_TIMER, /**< Start timer */ EV_STOP_TIMER, /**< Stop timer */ EV_ABORT_TRANS, /**< Abort transaction */ EV_STUN_REQUEST, /**< Outgoing STUN request */ EV_STUN_RESPONSE, /**< Received STUN response */ EV_NAT_KEEPALIVE, /**< Send a NAT keep alive packet */ EV_ICMP, /**< ICMP error */ EV_UI, /**< User interface event */ EV_ASYNC_RESPONSE, /**< Response on an asynchronous question */ EV_BROKEN_CONNECTION, /**< Persitent connection to SIP proxy broken */ EV_TCP_PING, /**< Send a TCP ping (double CRLF) */ EV_FN_CALL, /**< Queued function call */ }; /** Abstract parent class for all events */ class t_event { public: virtual ~t_event() {} /** Get the type of this event. */ virtual t_event_type get_type(void) const = 0; }; /** * Generic quit event. * The quit event instructs a thread to exit gracefully. */ class t_event_quit : public t_event { public: virtual ~t_event_quit(); virtual t_event_type get_type(void) const; }; /** * Network events. * A network event is a SIP message going from the transaction manager * to the network or v.v. */ class t_event_network : public t_event { private: /** The SIP message. */ t_sip_message *msg; public: unsigned int src_addr; /**< Source IP address of the SIP message (host order). */ unsigned short src_port; /**< Source port of the SIP message (host order). */ unsigned int dst_addr; /**< Destination IP address of the SIP message (host order). */ unsigned short dst_port; /**< Destination port of the SIP message (host order). */ string transport; /**< Transport protocol */ /** * Constructor. * The event will keep a copy of the SIP message. * @param m [in] The SIP message. */ t_event_network(t_sip_message *m); ~t_event_network(); t_event_type get_type(void) const; /** * Get the SIP message. * @return Pointer to the SIP message inside this event. */ t_sip_message *get_msg(void) const; }; /** * User events. * A user event is a SIP message going from the user to the * transaction manager or v.v. */ class t_event_user : public t_event { private: t_sip_message *msg; /**< The SIP message. */ unsigned short tuid; /**< Transaction user id. */ unsigned short tid; /**< Transaction id. */ /** * Transaction id that is the target of the CANCEL message. * Only set if tid is a CANCEL transaction and the event * is sent towards the user. */ unsigned short tid_cancel_target; /** User profile of the user sending/receiving the SIP message. */ t_user *user_config; public: /** Constructor. * @param u [in] User profile. * @param m [in] SIP message * @param _tuid [in] Transaction user id associated with this message. * @param _tid [in] Transaction id of the transaction for this message. */ t_event_user(t_user *u, t_sip_message *m, unsigned short _tuid, unsigned short _tid); /** Constructor for CANCEL request towards the user. * @param u [in] User profile. * @param m [in] SIP message. * @param _tuid [in] Transaction user id associated with this message. * @param _tid [in] Transaction id of the transaction for this message. * @param _tid_cancel_target [in] Id of the target transaction of a CANCEL request. */ t_event_user(t_user *u, t_sip_message *m, unsigned short _tuid, unsigned short _tid, unsigned short _tid_cancel_target); ~t_event_user(); t_event_type get_type(void) const; /** * Get the SIP message. * @return Pointer to the SIP message inside this event. */ t_sip_message *get_msg(void) const; /** Get transaction user id. */ unsigned short get_tuid(void) const; /** Get transaction id. */ unsigned short get_tid(void) const; /** Get the CANCEL target transaction id. */ unsigned short get_tid_cancel_target(void) const; /** * Get the user profile. * @return Pointer to the user profile inside the event. */ t_user *get_user_config(void) const; }; /** * Time out events. * Expiration of a timer is signalled by a time out event. */ class t_event_timeout : public t_event { private: /** * The epxired timer. * @note Timer pointer will be deleted upon destruction of the object. */ t_timer *timer; public: /** * Constructor. * @param t [in] The expired timer. * @note The event will keep a copy of the timer. */ t_event_timeout(t_timer *t); ~t_event_timeout(); t_event_type get_type(void) const; /** * Get the timer from the event. * @return The timer. */ t_timer *get_timer(void) const; }; /** * Failure events. */ class t_event_failure : public t_event { private: t_failure failure; /**< Type of failure. */ /** * Indicates if the tid value is populated. If the tid value is not * populated, then the branch and cseq_method are populated. */ bool tid_populated; unsigned short tid; /**< Id of transaction that failed. */ string branch; /**< Branch parameter of SIP message that failed. */ t_method cseq_method; /**< CSeq method of SIP message that failed. */ public: /** * Constructor. * @param f [in] Type of failure. * @param _tid [in] Transaction id. */ t_event_failure(t_failure f, unsigned short _tid); /** Constructor */ t_event_failure(t_failure f, const string &_branch, const t_method &_cseq_method); t_event_type get_type(void) const; /** * Get the type of failure. * @return Type of failure. */ t_failure get_failure(void) const; /** * Get the transaction id. * @return Transaction id. */ unsigned short get_tid(void) const; /** Get branch parameter. */ string get_branch(void) const; /** Get CSeq method. */ t_method get_cseq_method(void) const; /** Check if tid is populated. */ bool is_tid_populated(void) const; }; /** * Start timer event. * A start timer event instructs the time keeper to start a timer. */ class t_event_start_timer : public t_event { private: t_timer *timer; /**< The timer to start. */ public: /** * Constructor. * @param t [in] The timer to start. */ t_event_start_timer(t_timer *t); t_event_type get_type(void) const; /** * Get the timer. * @return Timer. */ t_timer *get_timer(void) const; }; /** * Stop timer event * A stop timer event instructs the time keeper to stop a timer. */ class t_event_stop_timer : public t_event { private: /** Id of the timer to stop. */ unsigned short timer_id; public: /** * Constructor. * @param id [in] Id of the timer to stop. */ t_event_stop_timer(unsigned short id); t_event_type get_type(void) const; /** * Get the timer id. * @return Timer id. */ unsigned short get_timer_id(void) const; }; /** * Abort transaction event. * With an abort transaction event, the requester asks the transaction * manager to abort a pending transaction. */ class t_event_abort_trans : public t_event { private: unsigned short tid; /**< Id of the transaction to abort. */ public: /** * Constructor. * @param _tid [in] Transaction id. */ t_event_abort_trans(unsigned short _tid); t_event_type get_type(void) const; /** * Get transaction id. * @return Transaction id. */ unsigned short get_tid(void) const; }; /** STUN event types. */ enum t_stun_event_type { TYPE_STUN_SIP, /**< Request to open a port for SIP. */ TYPE_STUN_MEDIA, /**< Request to open a port for media. */ }; /** * STUN request event. */ class t_event_stun_request : public t_event { private: StunMessage *msg; /**< STUN request to send. */ unsigned short tuid; /**< Transaction user id. */ unsigned short tid; /**< Transaction id. */ t_stun_event_type stun_event_type; /**< Type of STUN event. */ t_user *user_config; /**< User profile associated with this request. */ public: unsigned int dst_addr; /**< Destination address of request (host order). */ unsigned short dst_port; /**< Destination port of request (host order). */ unsigned short src_port; /**< Source port for media event type (host order). */ /** Constructor. */ t_event_stun_request(t_user *u, StunMessage *m, t_stun_event_type ev_type, unsigned short _tuid, unsigned short _tid); ~t_event_stun_request(); t_event_type get_type(void) const; /** Get STUN message. */ StunMessage *get_msg(void) const; /** Get transaction user id. */ unsigned short get_tuid(void) const; /** Get transaction id. */ unsigned short get_tid(void) const; /** Get STUN event type. */ t_stun_event_type get_stun_event_type(void) const; /** Get user profile. */ t_user *get_user_config(void) const; }; /** * STUN response event. */ class t_event_stun_response : public t_event { private: StunMessage *msg; /**< STUN request to send. */ unsigned short tuid; /**< Transaction user id. */ unsigned short tid; /**< Transaction id. */ public: /** Constructor. */ t_event_stun_response(StunMessage *m, unsigned short _tuid, unsigned short _tid); ~t_event_stun_response(); t_event_type get_type(void) const; /** Get STUN message. */ StunMessage *get_msg(void) const; /** Get transaction user id. */ unsigned short get_tuid(void) const; /** Get transaction id. */ unsigned short get_tid(void) const; }; /** * NAT keep alive event. * Request to send a NAT keep alive message. */ class t_event_nat_keepalive : public t_event { public: unsigned int dst_addr; /**< Destination address for keepalive (host order) */ unsigned short dst_port; /**< Destination port (host order) */ t_event_type get_type(void) const; }; /** * ICMP event. * This event signals the reception of an ICMP error. */ class t_event_icmp : public t_event { private: t_icmp_msg icmp; /**< The received ICMP message. */ public: /** * Constructor. * @param m [in] ICMP message. */ t_event_icmp(const t_icmp_msg &m); t_event_type get_type(void) const; /** * Get the ICMP message. * @return ICMP message. */ t_icmp_msg get_icmp(void) const; }; /** User interface callback types. */ enum t_ui_event_type { TYPE_UI_CB_DISPLAY_MSG, /**< Display a message */ TYPE_UI_CB_DTMF_DETECTED, /**< DTMF tone detected */ TYPE_UI_CB_SEND_DTMF, /**< Sending DTMF */ TYPE_UI_CB_RECV_CODEC_CHANGED, /**< Codec changed */ TYPE_UI_CB_LINE_STATE_CHANGED, /**< Line state changed */ TYPE_UI_CB_LINE_ENCRYPTED, /**< Line is now encrypted */ TYPE_UI_CB_SHOW_ZRTP_SAS, /**< Show the ZRTP SAS */ TYPE_UI_CB_ZRTP_CONFIRM_GO_CLEAR, /**< ZRTP Confirm go-clear */ TYPE_UI_CB_QUIT /**< Quit the user interface */ }; /** Display message priorities. */ enum t_msg_priority { MSG_NO_PRIO, MSG_INFO, MSG_WARNING, MSG_CRITICAL }; /** * User interface event. * Send a user interface callback to the user interface. * Most callbacks are called directly as a function call. * Sometimes an asynchronous callback is needed. That's where * this event is used for. */ class t_event_ui : public t_event { private: t_ui_event_type type; /**< User interface callback type. */ /** @name Parameters for call back functions */ //@{ int line; /**< Line number. */ t_audio_codec codec; /**< Audio codec. */ t_dtmf_ev dtmf_event; /**< DTMF event. */ bool encrypted; /**< Encryption indication. */ string cipher_mode; /**< Cipher mode (algorithm name). */ string zrtp_sas; /**< ZRTP SAS/ */ t_msg_priority msg_priority; /**< Priority of a display message. */ string msg; /**> Message to display. */ //@} public: /** * Constructor. * @param _type [in] Type of callback. */ t_event_ui(t_ui_event_type _type); t_event_type get_type(void) const; /** @name Set parameters for call back functions */ //@{ void set_line(int _line); void set_codec(t_audio_codec _codec); void set_dtmf_event(t_dtmf_ev _dtmf_event); void set_encrypted(bool on); void set_cipher_mode(const string &_cipher_mode); void set_zrtp_sas(const string &sas); void set_display_msg(const string &_msg, t_msg_priority &_msg_priority); //@} /** * Call the callback function. * @param user_intf [in] The user interface that receives the callback. */ void exec(t_userintf *user_intf); }; /** * Asynchronous response event. * A user interface can open an asynchronous message box to request * information from the user. Via this event the user interface signals * the response from the user. */ class t_event_async_response : public t_event { public: /** Response type */ enum t_response_type { RESP_REFER_PERMISSION /**< Response on permission to refer question */ }; private: t_response_type response_type; /**< Response type. */ bool bool_response; /**< Boolean response. */ public: /** * Constructor. * @param type [in] The response type. */ t_event_async_response(t_response_type type); t_event_type get_type(void) const; /** * Set the boolean response. * @param b [in] The response. */ void set_bool_response(bool b); /** * Get response type. * @return Response type. */ t_response_type get_response_type(void) const; /** * Get boolean response. * @return The response. */ bool get_bool_response(void) const; }; /** * Broken connection event. * A persistent connection to a SIP proxy is broken. With this event * the transport layer signals the transaction layer that a connection * is broken. */ class t_event_broken_connection : public t_event { private: /** The user URI (AoR) that the connection was associated with. */ t_url user_uri_; public: /** Constructor */ t_event_broken_connection(const t_url &url); t_event_type get_type(void) const; /** * Get the user URI. * @return The user URI. */ t_url get_user_uri(void) const; }; /** * TCP ping event. * Send a TCP ping (double CRLF). */ class t_event_tcp_ping : public t_event { private: /** The user URI (AoR) for which the ping must be sent. */ t_url user_uri_; unsigned int dst_addr_; /**< Destination address for ping (host order) */ unsigned short dst_port_; /**< Destination port (host order) */ public: /** Constructor */ t_event_tcp_ping(const t_url &url, unsigned int dst_addr, unsigned short dst_port); t_event_type get_type(void) const; /** @name Getters */ //@{ t_url get_user_uri(void) const; unsigned int get_dst_addr(void) const; unsigned short get_dst_port(void) const; //@} }; class t_event_fncall : public t_event { public: t_event_fncall(std::function fn); void invoke(); virtual t_event_type get_type(void) const override; private: std::function m_fn; }; /** * Event queue. * An event queue is the communication pipe between multiple * threads. Multiple threads write events into the queue and * one thread reads the events from the queue and processes them * Access to the queue is protected by a mutex. A semaphore is * used to synchronize the reader with the writers of the queue. */ class t_event_queue { private: queue ev_queue; /**< Queue of events. */ t_mutex mutex_evq; /**< Mutex to protect access to the queue. */ t_semaphore sema_evq; /**< Semephore counting the number of events. */ /** * Semaphore to signal an interrupt. * Will be posted when the interrupt method is called. */ t_semaphore sema_caught_interrupt; public: /** Constructor. */ t_event_queue(); ~t_event_queue(); /** * Push an event into the queue. * @param e [in] Event */ void push(t_event *e); /** Push a quit event into the queue. */ void push_quit(void); /** Push a queued function call */ void push_fncall(std::function fn); /** * Create a network event and push it into the queue. * @param m [in] SIP message. * @param ipaddr [in] Destination address of the message (host order). * @param port [in] Port of the message (host order). */ void push_network(t_sip_message *m, const t_ip_port &ip_port); /** * Create a user event and push it into the queue. * The user event must be associated with a user profile. * @param user_config [in] The user profile. * @param m [in] SIP message. * @param tuid [in] Transaction user id. * @param tid [in] Transaction id. */ void push_user(t_user *user_config, t_sip_message *m, unsigned short tuid, unsigned short tid); /** * Create a user event and push it into the queue. * The user event must be unrelated to a particular user profile. * @param m [in] SIP message. * @param tuid [in] Transaction user id. * @param tid [in] Transaction id. */ void push_user(t_sip_message *m, unsigned short tuid, unsigned short tid); /** * Create a cancel event for a user. * @param user_config [in] The user profile. * @param m [in] SIP message. * @param tuid [in] Transaction user id. * @param tid [in] Transaction id. */ void push_user_cancel(t_user *user_config, t_sip_message *m, unsigned short tuid, unsigned short tid, unsigned short target_tid); /** * Create a cancel event for a user. * @param m [in] SIP message. * @param tuid [in] Transaction user id. * @param tid [in] Transaction id. */ void push_user_cancel(t_sip_message *m, unsigned short tuid, unsigned short tid, unsigned short target_tid); /** * Create a timeout event and push it into the queue. * @param t [in] The timer that expired. */ void push_timeout(t_timer *t); /** * Create failure event and push it into the queue. * @param f [in] Type of failure. * @param tid [in] Transaction id of failed transaction. */ void push_failure(t_failure f, unsigned short tid); /** * Create failure event and push it into the queue. * @param f [in] Type of failure. * @param branch [in] Branch parameter of failed transaction. * @param cseq_method [in] CSeq method of failed transaction. */ void push_failure(t_failure f, const string &branch, const t_method &cseq_method); /** * Create a start timer event. * @param t [in] Timer to start. */ void push_start_timer(t_timer *t); /** * Create a stop timer event. * @param timer_id [in] Timer id of timer to stop. */ void push_stop_timer(unsigned short timer_id); /** * Create an abort transaction event. * @param tid [in] Transaction id of transaction to abort. */ void push_abort_trans(unsigned short tid); /** * Create a STUN request event. * @param user_config [in] The user profile associated with the request. * @param m [in] STUN request. * @param ev_type [in] Type of STUN event. * @param tuid [in] Transaction user id. * @param tid [in] Transaction id. * @param ipaddr [in] Destination address (host order) * @param port [in] Destination port (host order) * @param src_port [in] Source port of media. This must only be passed for * a media STUN event. */ void push_stun_request(t_user *user_config, StunMessage *m, t_stun_event_type ev_type, unsigned short tuid, unsigned short tid, unsigned long ipaddr, unsigned short port, unsigned short src_port = 0); /** * Create a STUN response event. * @param m [in] STUN response. * @param tuid [in] Transaction user id. * @param tid [in] Transaction id. */ void push_stun_response(StunMessage *m, unsigned short tuid, unsigned short tid); /** * Create a NAT keepalive event. * @param ipaddr [in] Destination address (host order) * @param port [in] Destination port (host order) */ void push_nat_keepalive(unsigned long ipaddr, unsigned short port); /** * Create ICMP event. * @param m [in] ICMP message. */ void push_icmp(const t_icmp_msg &m); /** * Create a REFER pemission response event. * @param permission [in] Permission allowed?. */ void push_refer_permission_response(bool permission); /** * Create a broken connection event. * @param user_uri [in] The user URI (AoR) associated with the connection. */ void push_broken_connection(const t_url &user_uri); /** * Create a TCP ping event. * @param user_uri [in] The user URI (AoR) for which the TCP ping must be sent. * @param dst_addr [in] The destination IPv4 address for the ping. * @param dst_port [in] The destination TCP port for the ping. */ void push_tcp_ping(const t_url &user_uri, unsigned int dst_addr, unsigned short dst_port); /** * Pop an event from the queue. * If the queue is empty then the thread will be blocked until an * event arrives. * @return The popped event. */ t_event *pop(void); /** * Pop an event from the queue. * Same method as above, but this one can be interrupted by * calling the method interrupt. * @param interrupted [out] When the pop operation is interrupted this * parameter is set to true. Otherwise it is false. * @return NULL, when interrupted. * @return The popped event, otherwise. */ t_event *pop(bool &interrupted); /** * Send an interrupt. * This will cause the interruptable pop to return. * A non-interruptable pop will ignore the interrupt. * If pop is currently not suspending the thread execution then the * next call to pop will catch the interrupt. */ void interrupt(void); }; #endif twinkle-1.10.1/src/exceptions.h000066400000000000000000000020561277565361200164110ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Exceptions. */ #ifndef _EXCEPTIONS_H #define _EXCEPTIONS_H #include /** Exception tupe. */ enum t_exception { X_DIALOG_ALREADY_ESTABLISHED, /**< Dialog is already established. */ X_WRONG_STATE /**< State machine is in wrong state. */ }; class empty_list_exception : public std::exception { }; #endif twinkle-1.10.1/src/gui/000077500000000000000000000000001277565361200146405ustar00rootroot00000000000000twinkle-1.10.1/src/gui/CMakeLists.txt000066400000000000000000000036471277565361200174120ustar00rootroot00000000000000project(twinkle-gui) include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) set(twinkle_ui_SRC addresscardform.ui authenticationform.ui buddyform.ui deregisterform.ui diamondcardprofileform.ui dtmfform.ui getaddressform.ui getprofilenameform.ui historyform.ui inviteform.ui logviewform.ui messageform.ui mphoneform.ui numberconversionform.ui redirectform.ui selectnicform.ui selectprofileform.ui selectuserform.ui sendfileform.ui srvredirectform.ui syssettingsform.ui termcapform.ui transferform.ui userprofileform.ui wizardform.ui ) set (twinkle_lang_SRC lang/twinkle_cs.ts lang/twinkle_de.ts lang/twinkle_fr.ts lang/twinkle_nl.ts lang/twinkle_ru.ts lang/twinkle_sv.ts ) qt5_wrap_ui( twinkle_UIS ${twinkle_ui_SRC} ) qt5_add_resources(twinkle_QRC icons.qrc qml/qml.qrc) qt5_add_translation(twinkle_LANG ${twinkle_lang_SRC} ) set(qt_LIBS Qt5::Widgets Qt5::Quick) set(CMAKE_AUTOMOC ON) set(TWINKLE_GUI-SRCS mphoneform.cpp inviteform.cpp getaddressform.cpp redirectform.cpp termcapform.cpp messageform.cpp srvredirectform.cpp userprofileform.cpp transferform.cpp syssettingsform.cpp historyform.cpp selectuserform.cpp selectprofileform.cpp buddyform.cpp diamondcardprofileform.cpp addresscardform.cpp authenticationform.cpp selectnicform.cpp sendfileform.cpp wizardform.cpp address_finder.cpp addresstablemodel.cpp buddylistview.cpp deregisterform.cpp dtmfform.cpp getprofilenameform.cpp gui.cpp logviewform.cpp main.cpp messageformview.cpp numberconversionform.cpp twinkleapplication.cpp yesnodialog.cpp textbrowsernoautolink.cpp osd.cpp incoming_call_popup.cpp ${twinkle_OBJS} ${twinkle_UIS} ${twinkle_QRC} ${twinkle_LANG} ) add_executable(twinkle ${TWINKLE_GUI-SRCS}) target_link_libraries(twinkle ${twinkle_LIBS} ${qt_LIBS}) install(TARGETS twinkle DESTINATION bin) install(FILES ${twinkle_LANG} DESTINATION share/twinkle/lang) twinkle-1.10.1/src/gui/address_finder.cpp000066400000000000000000000071141277565361200203230ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "address_finder.h" #include "gui.h" #include "log.h" t_address_finder *t_address_finder::instance = NULL; t_mutex t_address_finder::mtx_instance; t_address_finder::t_address_finder() { #ifdef HAVE_KDE // Load KAddressbook asynchronously. An LDAP address book // may take a while to load completely. This should not block // an incoming call. For the first call it may happen that an // address cannot be found as loading is still in progress. // This is an inconvenience, but will not harm the call. abook = KABC::StdAddressBook::self(true); connect(abook, SIGNAL(addressBookChanged(AddressBook *)), this, SLOT(invalidate_cache())); log_file->write_report("Preload KAddressbook.", "t_address_finder::t_address_finder"); #endif } void t_address_finder::find_address(t_user *user_config, const t_url &u) { if (u == last_url) return; last_url = u; last_name.clear(); last_photo = QImage(); #ifdef HAVE_KDE for (KABC::AddressBook::Iterator i = abook->begin(); i != abook->end(); i++) { // Normalize url using number conversion rules t_url u_normalized(u); u_normalized.apply_conversion_rules(user_config); KABC::PhoneNumber::List phoneNrs = i->phoneNumbers(); for (KABC::PhoneNumber::List::iterator j = phoneNrs.begin(); j != phoneNrs.end(); j++) { QString phone = (*j).number(); string full_address = ui->expand_destination( user_config, phone.ascii(), u_normalized.get_scheme()); t_url url_phone(full_address); if (!url_phone.is_valid()) continue; if (u_normalized.user_host_match(url_phone, user_config->get_remove_special_phone_symbols(), user_config->get_special_phone_symbols())) { last_name = i->realName().ascii(); last_photo = i->photo().data(); last_photo.detach(); // avoid sharing of QImage with kabc return; } } } #endif } void t_address_finder::preload(void) { // The address book is preloaded on creation of the // singleton instance. (void)t_address_finder::get_instance(); } t_address_finder *t_address_finder::get_instance(void) { mtx_instance.lock(); if (!instance) { instance = new t_address_finder(); // No MEMMAN audit as this instance will only be // cleaned up by process termination. } mtx_instance.unlock(); return instance; } string t_address_finder::find_name(t_user *user_config, const t_url &u) { mtx_finder.lock(); find_address(user_config, u); string name = last_name; mtx_finder.unlock(); return name; } QImage t_address_finder:: find_photo(t_user *user_config, const t_url &u) { mtx_finder.lock(); find_address(user_config, u); QImage photo = last_photo; mtx_finder.unlock(); return photo; } void t_address_finder::invalidate_cache(void) { mtx_finder.lock(); last_url.set_url(""); mtx_finder.unlock(); log_file->write_report("Address finder cache invalidated.", " t_address_finder::invalidate_cache", LOG_NORMAL, LOG_DEBUG); } twinkle-1.10.1/src/gui/address_finder.h000066400000000000000000000036301277565361200177670ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _ADDRESS_FINDER_H #define _ADDRESS_FINDER_H #include "twinkle_config.h" #include #include "user.h" #include "sockets/url.h" #include "threads/mutex.h" #include "qobject.h" #include "qimage.h" #ifdef HAVE_KDE #include #include #include #include #include #endif using namespace std; class t_address_finder : public QObject { private: Q_OBJECT static t_address_finder *instance; static t_mutex mtx_instance; t_mutex mtx_finder; #ifdef HAVE_KDE KABC::AddressBook *abook; #endif // Cached data for the last looked up URL. t_url last_url; string last_name; QImage last_photo; t_address_finder(); // Find the address based on a URL and put the found // data in the cache. void find_address(t_user *user_config, const t_url &u); public: // Preload KAddressbook static void preload(void); static t_address_finder *get_instance(void); // Find a name given a URL string find_name(t_user *user_config, const t_url &u); // Find a photo give a URL QImage find_photo(t_user *user_config, const t_url &u); public slots: void invalidate_cache(void); }; #endif twinkle-1.10.1/src/gui/addresscardform.cpp000066400000000000000000000055421277565361200205150ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "gui.h" #include "addresscardform.h" /* * Constructs a AddressCardForm as a child of 'parent', with the * name 'name' and widget flags set to 'f'. * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ AddressCardForm::AddressCardForm(QWidget* parent) : QDialog(parent) { setupUi(this); } /* * Destroys the object and frees any allocated resources */ AddressCardForm::~AddressCardForm() { // no need to delete child widgets, Qt does it all for us } /* * Sets the strings of the subwidgets using the current * language. */ void AddressCardForm::languageChange() { retranslateUi(this); } int AddressCardForm::exec(t_address_card &card) { firstNameLineEdit->setText(card.name_first.c_str()); infixNameLineEdit->setText(card.name_infix.c_str()); lastNameLineEdit->setText(card.name_last.c_str()); phoneLineEdit->setText(card.sip_address.c_str()); remarkLineEdit->setText(card.remark.c_str()); int retval = QDialog::exec(); if (retval == QDialog::Accepted) { card.name_first = firstNameLineEdit->text().trimmed().toStdString(); card.name_infix = infixNameLineEdit->text().trimmed().toStdString(); card.name_last = lastNameLineEdit->text().trimmed().toStdString(); card.sip_address = phoneLineEdit->text().trimmed().toStdString(); card.remark = remarkLineEdit->text().trimmed().toStdString(); } return retval; } void AddressCardForm::validate() { QString firstName = firstNameLineEdit->text().trimmed(); QString infixName = infixNameLineEdit->text().trimmed(); QString lastName = lastNameLineEdit->text().trimmed(); QString phone = phoneLineEdit->text().trimmed(); if (firstName.isEmpty() && infixName.isEmpty() && lastName.isEmpty()) { ((t_gui *)ui)->cb_show_msg(this, tr("You must fill in a name.").toStdString(), MSG_CRITICAL); firstNameLineEdit->setFocus(); return; } if (phone.isEmpty()) { ((t_gui *)ui)->cb_show_msg(this, tr("You must fill in a phone number or SIP address.").toStdString(), MSG_CRITICAL); phoneLineEdit->setFocus(); return; } accept(); } twinkle-1.10.1/src/gui/addresscardform.h000066400000000000000000000006321277565361200201550ustar00rootroot00000000000000#ifndef ADDRESSCARDFORM_H #define ADDRESSCARDFORM_H #include "address_book.h" #include "ui_addresscardform.h" class AddressCardForm : public QDialog, public Ui::AddressCardForm { Q_OBJECT public: AddressCardForm(QWidget* parent = 0); ~AddressCardForm(); virtual int exec( t_address_card & card ); public slots: virtual void validate(); protected slots: virtual void languageChange(); }; #endif twinkle-1.10.1/src/gui/addresscardform.ui000066400000000000000000000156541277565361200203550ustar00rootroot00000000000000 AddressCardForm 0 0 604 209 Twinkle - Address Card &Remark: remarkLineEdit false Infix name of contact. First name of contact. &First name: firstNameLineEdit false You may place any remark about the contact here. &Phone: phoneLineEdit false &Infix name: infixNameLineEdit false Phone number or SIP address of contact. Last name of contact. &Last name: lastNameLineEdit false 20 31 QSizePolicy::Expanding Qt::Vertical 261 20 QSizePolicy::Expanding Qt::Horizontal &OK Alt+O true &Cancel Alt+C firstNameLineEdit infixNameLineEdit lastNameLineEdit phoneLineEdit remarkLineEdit okPushButton cancelPushButton address_book.h okPushButton clicked() AddressCardForm validate() cancelPushButton clicked() AddressCardForm reject() twinkle-1.10.1/src/gui/addresstablemodel.cpp000066400000000000000000000071261277565361200210300ustar00rootroot00000000000000/* Copyright (C) 2015 Lubos Dolezel 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, see . */ #include "addresstablemodel.h" #include AddressTableModel::AddressTableModel(QObject *parent, const list& data) : QAbstractTableModel(parent) { m_data = QList::fromStdList(data); } int AddressTableModel::rowCount(const QModelIndex &parent) const { return m_data.size(); } int AddressTableModel::columnCount(const QModelIndex &parent) const { return 3; } QVariant AddressTableModel::data(const QModelIndex &index, int role) const { if (role != Qt::DisplayRole) return QVariant(); const int row = index.row(); switch (index.column()) { case COL_ADDR_NAME: return QString::fromStdString(m_data[row].get_display_name()); case COL_ADDR_PHONE: return QString::fromStdString(m_data[row].sip_address); case COL_ADDR_REMARK: return QString::fromStdString(m_data[row].remark); default: return QVariant(); } } QVariant AddressTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); switch (section) { case COL_ADDR_NAME: return tr("Name"); case COL_ADDR_PHONE: return tr("Phone"); case COL_ADDR_REMARK: return tr("Remark"); default: return QVariant(); } } void AddressTableModel::appendAddress(const t_address_card& card) { qDebug() << "Appending contact" << QString::fromStdString(card.get_display_name()); beginInsertRows(QModelIndex(), m_data.size(), m_data.size()); m_data << card; endInsertRows(); } void AddressTableModel::removeAddress(int index) { beginRemoveRows(QModelIndex(), index, index); m_data.removeAt(index); endRemoveRows(); } void AddressTableModel::modifyAddress(int index, const t_address_card& card) { m_data[index] = card; emit dataChanged(createIndex(index, 0), createIndex(index, 2)); } void AddressTableModel::sort(int column, Qt::SortOrder order) { qSort(m_data.begin(), m_data.end(), [=](const t_address_card& a1, const t_address_card& a2) -> bool { bool retval = false; switch (column) { case COL_ADDR_NAME: if (order == Qt::DescendingOrder) retval = QString::fromStdString(a1.get_display_name()) > QString::fromStdString(a2.get_display_name()); else retval = QString::fromStdString(a1.get_display_name()) < QString::fromStdString(a2.get_display_name()); break; case COL_ADDR_PHONE: if (order == Qt::DescendingOrder) retval = a1.sip_address.compare(a2.sip_address) > 0; else retval = a1.sip_address.compare(a2.sip_address) < 0; break; case COL_ADDR_REMARK: if (order == Qt::DescendingOrder) retval = QString::fromStdString(a1.remark) > QString::fromStdString(a2.remark); else retval = QString::fromStdString(a1.remark) < QString::fromStdString(a2.remark); break; } return retval; }); emit dataChanged(createIndex(0, 0), createIndex(m_data.size()-1, 2)); } twinkle-1.10.1/src/gui/addresstablemodel.h000066400000000000000000000031671277565361200204760ustar00rootroot00000000000000/* Copyright (C) 2015 Lubos Dolezel 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, see . */ #ifndef _ADDRESSTABLEMODEL_H #define _ADDRESSTABLEMODEL_H #include "address_book.h" #include #include // Columns #define COL_ADDR_NAME 0 #define COL_ADDR_PHONE 1 #define COL_ADDR_REMARK 2 class AddressTableModel : public QAbstractTableModel { private: QList m_data; public: AddressTableModel(QObject *parent, const list& data); virtual int rowCount(const QModelIndex &parent) const; virtual int columnCount(const QModelIndex &parent) const; virtual QVariant data(const QModelIndex &index, int role) const; virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; virtual void sort(int column, Qt::SortOrder order); void appendAddress(const t_address_card& card); void removeAddress(int index); void modifyAddress(int index, const t_address_card& card); t_address_card getAddress(int index) { return m_data[index]; } }; #endif twinkle-1.10.1/src/gui/authenticationform.cpp000066400000000000000000000036751277565361200212620ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "authenticationform.h" /* * Constructs a AuthenticationForm as a child of 'parent', with the * name 'name' and widget flags set to 'f'. * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ AuthenticationForm::AuthenticationForm(QWidget* parent) : QDialog(parent) { setupUi(this); } /* * Destroys the object and frees any allocated resources */ AuthenticationForm::~AuthenticationForm() { // no need to delete child widgets, Qt does it all for us } /* * Sets the strings of the subwidgets using the current * language. */ void AuthenticationForm::languageChange() { retranslateUi(this); } int AuthenticationForm::exec(t_user *user_config, const QString &realm, QString &username, QString &password) { int retval; profileValueTextLabel->setText(user_config->get_profile_name().c_str()); userValueTextLabel->setText(user_config->get_display_uri().c_str()); realmTextLabel->setText(realm); usernameLineEdit->setText(username); passwordLineEdit->setText(password); if (!username.isEmpty()) passwordLineEdit->setFocus(); retval = QDialog::exec(); username = usernameLineEdit->text(); password = passwordLineEdit->text(); return retval; } twinkle-1.10.1/src/gui/authenticationform.h000066400000000000000000000006741277565361200207230ustar00rootroot00000000000000#ifndef AUTHENTICATIONFORM_H #define AUTHENTICATIONFORM_H #include "user.h" #include "ui_authenticationform.h" class AuthenticationForm : public QDialog, public Ui::AuthenticationForm { Q_OBJECT public: AuthenticationForm(QWidget* parent = 0); ~AuthenticationForm(); virtual int exec( t_user * user_config, const QString & realm, QString & username, QString & password ); protected slots: virtual void languageChange(); }; #endif twinkle-1.10.1/src/gui/authenticationform.ui000066400000000000000000000231521277565361200211050ustar00rootroot00000000000000 AuthenticationForm 0 0 582 198 Twinkle - Authentication :/icons/images/password.png false 20 51 QSizePolicy::Expanding Qt::Vertical user The user for which authentication is requested. false 7 0 0 0 profile The user profile of the user for which authentication is requested. false User profile: false User: false &Password: passwordLineEdit false 200 0 QLineEdit::Password Your password for authentication. 200 0 Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. &User name: usernameLineEdit false 20 16 QSizePolicy::Expanding Qt::Vertical 101 20 QSizePolicy::Expanding Qt::Horizontal &OK true &Cancel 32767 32767 Login required for realm: false 7 0 0 0 realm The realm for which you need to authenticate. false usernameLineEdit passwordLineEdit okPushButton cancelPushButton user.h okPushButton clicked() AuthenticationForm accept() cancelPushButton clicked() AuthenticationForm reject() twinkle-1.10.1/src/gui/buddyform.cpp000066400000000000000000000111761277565361200173450ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "gui.h" #include "sockets/url.h" #include "buddylistview.h" #include "audits/memman.h" #include "buddyform.h" /* * Constructs a BuddyForm as a child of 'parent', with the * name 'name' and widget flags set to 'f'. * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ BuddyForm::BuddyForm(QWidget* parent) : QDialog(parent) { setupUi(this); init(); } /* * Destroys the object and frees any allocated resources */ BuddyForm::~BuddyForm() { destroy(); // no need to delete child widgets, Qt does it all for us } /* * Sets the strings of the subwidgets using the current * language. */ void BuddyForm::languageChange() { retranslateUi(this); } void BuddyForm::init() { getAddressForm = 0; } void BuddyForm::destroy() { if (getAddressForm) { MEMMAN_DELETE(getAddressForm); delete getAddressForm; } } void BuddyForm::showNew(t_buddy_list &_buddy_list, QTreeWidgetItem *_profileItem) { user_config = _buddy_list.get_user_profile(); edit_mode = false; buddy_list = &_buddy_list; profileItem = _profileItem; QDialog::show(); } void BuddyForm::showEdit(t_buddy &buddy) { user_config = buddy.get_user_profile(); edit_mode = true; edit_buddy = &buddy; buddy_list = edit_buddy->get_buddy_list(); nameLineEdit->setText(buddy.get_name().c_str()); phoneLineEdit->setText(buddy.get_sip_address().c_str()); subscribeCheckBox->setChecked(buddy.get_may_subscribe_presence()); phoneLineEdit->setEnabled(false); phoneTextLabel->setEnabled(false); addressToolButton->hide(); QDialog::show(); } void BuddyForm::validate() { QString name = nameLineEdit->text().trimmed(); QString address = phoneLineEdit->text().trimmed(); if (name.isEmpty()) { ((t_gui *)ui)->cb_show_msg(this, tr("You must fill in a name.").toStdString(), MSG_CRITICAL); nameLineEdit->setFocus(); return; } string dest = ui->expand_destination(user_config, address.toStdString()); t_url dest_url(dest); if (!dest_url.is_valid()) { ((t_gui *)ui)->cb_show_msg(this, tr("Invalid phone.").toStdString(), MSG_CRITICAL); phoneLineEdit->setFocus(); return; } if (edit_mode) { // Edit existing buddy bool must_subscribe = false; bool must_unsubscribe = false; if (edit_buddy->get_may_subscribe_presence() != subscribeCheckBox->isChecked()) { if (subscribeCheckBox->isChecked()) { must_subscribe = true;; } else { must_unsubscribe = true; } } edit_buddy->set_name(nameLineEdit->text().trimmed().toStdString()); edit_buddy->set_sip_address(phoneLineEdit->text().trimmed().toStdString()); edit_buddy->set_may_subscribe_presence(subscribeCheckBox->isChecked()); if (must_subscribe) edit_buddy->subscribe_presence(); if (must_unsubscribe) edit_buddy->unsubscribe_presence(); } else { // Add a new buddy t_buddy buddy; buddy.set_name(nameLineEdit->text().trimmed().toStdString()); buddy.set_sip_address(phoneLineEdit->text().trimmed().toStdString()); buddy.set_may_subscribe_presence(subscribeCheckBox->isChecked()); t_buddy *new_buddy = buddy_list->add_buddy(buddy); new BuddyListViewItem(profileItem, new_buddy); new_buddy->subscribe_presence(); } string err_msg; if (!buddy_list->save(err_msg)) { QString msg = tr("Failed to save buddy list: %1").arg(err_msg.c_str()); ((t_gui *)ui)->cb_show_msg(this, msg.toStdString(), MSG_CRITICAL); } accept(); } void BuddyForm::showAddressBook() { if (!getAddressForm) { getAddressForm = new GetAddressForm(this); getAddressForm->setModal(true); MEMMAN_NEW(getAddressForm); } connect(getAddressForm, SIGNAL(address(const QString &, const QString &)), this, SLOT(selectedAddress(const QString &, const QString &))); getAddressForm->show(); } void BuddyForm::selectedAddress(const QString &name, const QString &phone) { nameLineEdit->setText(name); phoneLineEdit->setText(phone); } twinkle-1.10.1/src/gui/buddyform.h000066400000000000000000000015061277565361200170060ustar00rootroot00000000000000#ifndef BUDDYFORM_H #define BUDDYFORM_H #include "getaddressform.h" #include "presence/buddy.h" #include #include "user.h" #include "ui_buddyform.h" class BuddyForm : public QDialog, public Ui::BuddyForm { Q_OBJECT public: BuddyForm(QWidget* parent = 0); ~BuddyForm(); public slots: virtual void showNew( t_buddy_list & _buddy_list, QTreeWidgetItem * _profileItem ); virtual void showEdit( t_buddy & buddy ); virtual void validate(); virtual void showAddressBook(); virtual void selectedAddress( const QString & name, const QString & phone ); protected slots: virtual void languageChange(); private: GetAddressForm *getAddressForm; t_user *user_config; bool edit_mode; t_buddy_list *buddy_list; t_buddy *edit_buddy; QTreeWidgetItem *profileItem; void init(); void destroy(); }; #endif twinkle-1.10.1/src/gui/buddyform.ui000066400000000000000000000155601277565361200172010ustar00rootroot00000000000000 BuddyForm 0 0 484 154 Twinkle - Buddy 20 47 QSizePolicy::Expanding Qt::Vertical Qt::TabFocus :/icons/images/kontact_contacts.png Address book Select an address from the address book. &Phone: phoneLineEdit false Name of your buddy. &Show availability Alt+S true Check this option if you want to see the availability of your buddy. This will only work if your provider offers a presence agent. &Name: nameLineEdit false SIP address your buddy. 20 16 QSizePolicy::Expanding Qt::Vertical 131 20 QSizePolicy::Expanding Qt::Horizontal &OK Alt+O true &Cancel Alt+C nameLineEdit phoneLineEdit subscribeCheckBox addressToolButton okPushButton cancelPushButton presence/buddy.h user.h getaddressform.h okPushButton clicked() BuddyForm validate() cancelPushButton clicked() BuddyForm reject() addressToolButton clicked() BuddyForm showAddressBook() twinkle-1.10.1/src/gui/buddylistview.cpp000066400000000000000000000166601277565361200202530ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "buddylistview.h" #include "gui.h" #include "qapplication.h" #include "qfont.h" #include "qpixmap.h" #include "qrect.h" #include "qsize.h" #include void AbstractBLVItem::set_icon(t_presence_state::t_basic_state state) { switch (state) { case t_presence_state::ST_BASIC_UNKNOWN: setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_unknown.png")); break; case t_presence_state::ST_BASIC_CLOSED: setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_offline.png")); break; case t_presence_state::ST_BASIC_OPEN: setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_online.png")); break; case t_presence_state::ST_BASIC_FAILED: setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_failed.png")); break; case t_presence_state::ST_BASIC_REJECTED: setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_rejected.png")); break; default: setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_unknown.png")); break; } } AbstractBLVItem::AbstractBLVItem(QTreeWidgetItem *parent, const QString &text) : QTreeWidgetItem(parent, QStringList(text)) {} AbstractBLVItem::AbstractBLVItem(QTreeWidget *parent, const QString &text) : QTreeWidgetItem(parent, QStringList(text)) {} AbstractBLVItem::~AbstractBLVItem() {} QString AbstractBLVItem::get_tip(void) { return tip; } void BuddyListViewItem::set_icon(void) { t_user *user_config = buddy->get_user_profile(); string url_str = ui->expand_destination(user_config, buddy->get_sip_address()); QString address = QString::fromStdString(ui->format_sip_address(user_config, buddy->get_name(), t_url(url_str))); tip = ""; #if QT_VERSION >= 0x050000 tip += address.toHtmlEscaped().replace(' ', " "); #else tip += Qt::escape(address).replace(' ', " "); #endif if (!buddy->get_may_subscribe_presence()) { setData(0, Qt::DecorationRole, QPixmap(":/icons/images/buddy.png")); } else { QString failure; t_presence_state::t_basic_state basic_state = buddy-> get_presence_state()->get_basic_state(); AbstractBLVItem::set_icon(basic_state); tip += "
"; tip += ""; tip += qApp->translate("BuddyList", "Availability"); tip += ": "; switch (basic_state) { case t_presence_state::ST_BASIC_UNKNOWN: tip += qApp->translate("BuddyList", "unknown"); break; case t_presence_state::ST_BASIC_CLOSED: tip += qApp->translate("BuddyList", "offline"); break; case t_presence_state::ST_BASIC_OPEN: tip += qApp->translate("BuddyList", "online"); break; case t_presence_state::ST_BASIC_FAILED: tip += qApp->translate("BuddyList", "request failed"); failure = QString::fromStdString(buddy->get_presence_state()->get_failure_msg()); if (!failure.isEmpty()) { tip += QString(" (%1)").arg(failure); } break; case t_presence_state::ST_BASIC_REJECTED: tip += qApp->translate("BuddyList", "request rejected"); break; default: tip += qApp->translate("BuddyList", "unknown"); break; } } tip += ""; tip = tip.replace(' ', " "); } BuddyListViewItem::BuddyListViewItem(QTreeWidgetItem *parent, t_buddy *_buddy) : AbstractBLVItem(parent, QString::fromStdString(_buddy->get_name())), buddy(_buddy) { set_icon(); buddy->attach(this); QObject::connect(this, SIGNAL(update_signal()), this, SLOT(update_slot())); } BuddyListViewItem::~BuddyListViewItem() { buddy->detach(this); } void BuddyListViewItem::update_slot(void) { // This method is called directly from the core, so lock the GUI ui->lock(); set_icon(); if (buddy->get_name().c_str() != text(0)) { setText(0, QString::fromStdString(buddy->get_name())); QTreeWidgetItem::treeWidget()->sortItems(0, Qt::AscendingOrder); } ui->unlock(); } void BuddyListViewItem::update(void) { emit update_signal(); } void BuddyListViewItem::subject_destroyed(void) { delete this; } t_buddy *BuddyListViewItem::get_buddy(void) { return buddy; } void BLViewUserItem::set_icon(void) { t_presence_state::t_basic_state basic_state; QString failure; QString profile_name = QString::fromStdString(presence_epa->get_user_profile()->get_profile_name()); tip = ""; #if QT_VERSION >= 0x050000 tip += profile_name.toHtmlEscaped(); #else tip += Qt::escape(profile_name); #endif tip += "
"; tip += ""; tip += qApp->translate("BuddyList", "Availability"); tip += ": "; switch (presence_epa->get_epa_state()) { case t_presence_epa::EPA_UNPUBLISHED: tip += qApp->translate("BuddyList", "not published"); setData(0, Qt::DecorationRole, QPixmap(":/icons/images/penguin-small.png")); break; case t_presence_epa::EPA_FAILED: tip += qApp->translate("BuddyList", "failed to publish"); failure = presence_epa->get_failure_msg().c_str(); if (!failure.isEmpty()) { tip += QString(" (%1)").arg(failure); } setData(0, Qt::DecorationRole, QPixmap(":/icons/images/presence_failed.png")); break; case t_presence_epa::EPA_PUBLISHED: basic_state = presence_epa->get_basic_state(); AbstractBLVItem::set_icon(basic_state); switch (presence_epa->get_basic_state()) { case t_presence_state::ST_BASIC_CLOSED: tip += qApp->translate("BuddyList", "offline"); break; case t_presence_state::ST_BASIC_OPEN: tip += qApp->translate("BuddyList", "online"); break; default: tip += qApp->translate("BuddyList", "unknown"); break; } break; default: tip += qApp->translate("BuddyList", "unknown"); break; } tip += "

"; tip += qApp->translate("BuddyList", "Click right to add a buddy."); tip += ""; tip = tip.replace(' ', " "); } BLViewUserItem::BLViewUserItem(QTreeWidget *parent, t_presence_epa *_presence_epa) : AbstractBLVItem(parent, QString::fromStdString(_presence_epa->get_user_profile()->get_profile_name())), presence_epa(_presence_epa) { set_icon(); presence_epa->attach(this); QObject::connect(this, SIGNAL(update_signal()), this, SLOT(update_slot())); QFont font = this->font(0); font.setBold(true); this->setFont(0, font); } BLViewUserItem::~BLViewUserItem() { presence_epa->detach(this); } void BLViewUserItem::update_slot(void) { // This method is called directly from the core, so lock the GUI ui->lock(); set_icon(); if (presence_epa->get_user_profile()->get_profile_name().c_str() == text(0)) { setText(0, QString::fromStdString(presence_epa->get_user_profile()->get_profile_name())); QTreeWidgetItem::treeWidget()->sortItems(0, Qt::AscendingOrder); } ui->unlock(); } void BLViewUserItem::update(void) { emit update_signal(); } void BLViewUserItem::subject_destroyed(void) { delete this; } t_presence_epa *BLViewUserItem::get_presence_epa(void) { return presence_epa; } twinkle-1.10.1/src/gui/buddylistview.h000066400000000000000000000045641277565361200177200ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef BUDDYLISTVIEW_H #define BUDDYLISTVIEW_H #include #include "qpainter.h" #include "qtooltip.h" #include "presence/buddy.h" #include "presence/presence_epa.h" #include "patterns/observer.h" class AbstractBLVItem : public QTreeWidgetItem { protected: // Text to show as a tool tip. QString tip; // Set the presence icon to reflect the presence state virtual void set_icon(t_presence_state::t_basic_state state); public: AbstractBLVItem(QTreeWidgetItem *parent, const QString &text); AbstractBLVItem(QTreeWidget *parent, const QString &text); virtual ~AbstractBLVItem(); virtual QString get_tip(void); }; // List view item representing a buddy. class BuddyListViewItem : public QObject, public AbstractBLVItem, public patterns::t_observer { Q_OBJECT private: t_buddy *buddy; // Set the presence icon to reflect the buddy's presence void set_icon(void); public: BuddyListViewItem(QTreeWidgetItem *parent, t_buddy *_buddy); virtual ~BuddyListViewItem(); virtual void update(void); virtual void subject_destroyed(void); t_buddy *get_buddy(void); signals: void update_signal(); private slots: void update_slot(); }; // List view item representing a user class BLViewUserItem : public QObject, public AbstractBLVItem, public patterns::t_observer { Q_OBJECT private: t_presence_epa *presence_epa; void set_icon(void); public: BLViewUserItem(QTreeWidget *parent, t_presence_epa *_presence_epa); virtual ~BLViewUserItem(); virtual void update(void); virtual void subject_destroyed(void); t_presence_epa *get_presence_epa(void); signals: void update_signal(); private slots: void update_slot(); }; #endif twinkle-1.10.1/src/gui/command_args.h000066400000000000000000000034041277565361200174440ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _COMMAND_ARGS_H #define _COMMAND_ARGS_H #include "twinkle_config.h" /** Command arguments. */ struct t_command_args { /** SIP URI to be called passed via the --call command line parameter. */ QString callto_destination; /** CLI command passed via the --cmd command line parameter. */ QString cli_command; /** Indicates if the --call or --cmd must be performed immediately. */ bool cmd_immediate_mode; /** Indicates the profile that should be made active before performing * --call or --cmd */ QString cmd_set_profile; /** Indicates if the --show option was given. */ bool cmd_show; /** Indicates if the --hide option was given. */ bool cmd_hide; /** If a port number is passed by the user on the command line, then * that port number overrides the port from the system settings. */ unsigned short override_sip_port; unsigned short override_rtp_port; t_command_args() : cmd_immediate_mode(false), cmd_show(false), cmd_hide(false), override_sip_port(0), override_rtp_port(0) {} }; #endif twinkle-1.10.1/src/gui/core_strings.h000066400000000000000000000143611277565361200175170ustar00rootroot00000000000000// This file is generated by translator.py // It contains all strings that need translation from the // core of Twinkle. #define _ZAP(s) // userintf.cpp _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Anonymous")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Warning:")) // log.cpp _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Failed to create log file %1 .")) // call_history.cpp _ZAP(QT_TRANSLATE_NOOP("CoreCallHistory", "local user")) _ZAP(QT_TRANSLATE_NOOP("CoreCallHistory", "remote user")) _ZAP(QT_TRANSLATE_NOOP("CoreCallHistory", "failure")) _ZAP(QT_TRANSLATE_NOOP("CoreCallHistory", "unknown")) _ZAP(QT_TRANSLATE_NOOP("CoreCallHistory", "in")) _ZAP(QT_TRANSLATE_NOOP("CoreCallHistory", "out")) _ZAP(QT_TRANSLATE_NOOP("CoreCallHistory", "unknown")) // sender.cpp _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Excessive number of socket errors.")) // sys_settings.cpp _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Built with support for:")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Contributions:")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "This software contains the following software from 3rd parties:")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "* GSM codec from Jutta Degener and Carsten Bormann, University of Berlin")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "* G.711/G.726 codecs from Sun Microsystems (public domain)")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "* iLBC implementation from RFC 3951 (www.ilbcfreeware.org)")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "* Parts of the STUN project at http://sourceforge.net/projects/stun")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "* Parts of libsrv at http://libsrv.sourceforge.net/")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "For RTP the following dynamic libraries are linked:")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Translated to english by ")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Directory %1 does not exist.")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot open file %1 .")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot open file %1 .")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "%1 is not set to your home directory.")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Directory %1 (%2) does not exist.")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot create directory %1 .")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot create directory %1 .")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Failed to create file %1")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Failed to write data to file %1")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot create %1 .")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "%1 is already running.\nLock file %2 already exists.")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot lock %1 .")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot open file for reading: %1")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "File system error while reading file %1 .")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Syntax error in file %1 .")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Failed to backup %1 to %2")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot open file for writing: %1")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "File system error while writing file %1 .")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "unknown name (device is busy)")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Default device")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot access the ring tone device (%1).")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot access the speaker (%1).")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot access the microphone (%1).")) // listener.cpp _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Excessive number of socket errors.")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot receive incoming TCP connections.")) // phone.cpp _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Call transfer - %1")) // audio_session.cpp _ZAP(QT_TRANSLATE_NOOP("CoreAudio", "Failed to open sound card")) _ZAP(QT_TRANSLATE_NOOP("CoreAudio", "Failed to open sound card")) _ZAP(QT_TRANSLATE_NOOP("CoreAudio", "Failed to open sound card")) _ZAP(QT_TRANSLATE_NOOP("CoreAudio", "Failed to create a UDP socket (RTP) on port %1")) _ZAP(QT_TRANSLATE_NOOP("CoreAudio", "Failed to create audio receiver thread.")) _ZAP(QT_TRANSLATE_NOOP("CoreAudio", "Failed to create audio transmitter thread.")) // audio_device.cpp _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Sound card cannot be set to full duplex.")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot set buffer size on sound card.")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Sound card cannot be set to %1 channels.")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Sound card cannot be set to %1 channels.")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot set sound card to 16 bits recording.")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot set sound card to 16 bits playing.")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot set sound card sample rate to %1")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Opening ALSA driver failed")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot open ALSA driver for PCM playback")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot open ALSA driver for PCM capture")) // msg_session.cpp _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Failed to send message.")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Failed to send message.")) // stun_transaction.cpp _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot resolve STUN server: %1")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "You are behind a symmetric NAT.\nSTUN will not work.\nConfigure a public IP address in the user profile\nand create the following static bindings (UDP) in your NAT.")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "public IP: %1 --> private IP: %2 (SIP signaling)")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "public IP: %1-%2 --> private IP: %3-%4 (RTP/RTCP)")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot reach the STUN server: %1")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "If you are behind a firewall then you need to open the following UDP ports.")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Port %1 (SIP signaling)")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Ports %1-%2 (RTP/RTCP)")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "NAT type discovery via STUN failed.")) // record_file.hpp _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot open file for reading: %1")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "File system error while reading file %1 .")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "Cannot open file for writing: %1")) _ZAP(QT_TRANSLATE_NOOP("TwinkleCore", "File system error while writing file %1 .")) twinkle-1.10.1/src/gui/deregisterform.cpp000066400000000000000000000010001277565361200203540ustar00rootroot00000000000000#include "deregisterform.h" /* * Constructs a DeregisterForm which is a child of 'parent', with the * name 'name' and widget flags set to 'f' * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ DeregisterForm::DeregisterForm(QWidget* parent) : QDialog(parent) { setupUi(this); } /* * Destroys the object and frees any allocated resources */ DeregisterForm::~DeregisterForm() { // no need to delete child widgets, Qt does it all for us } twinkle-1.10.1/src/gui/deregisterform.h000066400000000000000000000004401277565361200200300ustar00rootroot00000000000000#ifndef DEREGISTERFORM_H #define DEREGISTERFORM_H #include #include "ui_deregisterform.h" class DeregisterForm : public QDialog, private Ui::DeregisterForm { Q_OBJECT public: DeregisterForm(QWidget* parent = 0); ~DeregisterForm(); }; #endif // DEREGISTERFORM_H twinkle-1.10.1/src/gui/deregisterform.ui000066400000000000000000000050001277565361200202130ustar00rootroot00000000000000 DeregisterForm 0 0 287 82 0 0 0 0 Twinkle - Deregister deregister all devices 111 20 QSizePolicy::Expanding Qt::Horizontal &OK true &Cancel okPushButton clicked() DeregisterForm accept() cancelPushButton clicked() DeregisterForm reject() twinkle-1.10.1/src/gui/diamondcardprofileform.cpp000066400000000000000000000113241277565361200220570ustar00rootroot00000000000000//Added by qt3to4: #include #include /* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include "gui.h" #include "diamondcard.h" #include "getprofilenameform.h" #include "audits/memman.h" #include "diamondcardprofileform.h" /* * Constructs a DiamondcardProfileForm as a child of 'parent', with the * name 'name' and widget flags set to 'f'. * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ DiamondcardProfileForm::DiamondcardProfileForm(QWidget* parent) : QDialog(parent) { setupUi(this); init(); } /* * Destroys the object and frees any allocated resources */ DiamondcardProfileForm::~DiamondcardProfileForm() { destroy(); // no need to delete child widgets, Qt does it all for us } /* * Sets the strings of the subwidgets using the current * language. */ void DiamondcardProfileForm::languageChange() { retranslateUi(this); } void DiamondcardProfileForm::init() { user_config = NULL; destroy_user_config = false; QRegExp rxNoSpace("\\S*"); accountIdLineEdit->setValidator(new QRegExpValidator(rxNoSpace, this)); pinCodeLineEdit->setValidator(new QRegExpValidator(rxNoSpace, this)); } void DiamondcardProfileForm::destroy() { destroyOldUserConfig(); } void DiamondcardProfileForm::destroyOldUserConfig() { if (user_config && destroy_user_config) { MEMMAN_DELETE(user_config); delete user_config; } user_config = NULL; } // Show the form void DiamondcardProfileForm::show(t_user *user) { destroyOldUserConfig(); if (user) { user_config = user; destroy_user_config = false; } else { user_config = new t_user(); MEMMAN_NEW(user_config); destroy_user_config = true; } QDialog::show(); } // Modal execution int DiamondcardProfileForm::exec(t_user *user) { destroyOldUserConfig(); user_config = user; destroy_user_config = false; return QDialog::exec(); } void DiamondcardProfileForm::validate() { if (accountIdLineEdit->text().isEmpty()) { ((t_gui *)ui)->cb_show_msg(this, tr("Fill in your account ID.").toStdString(), MSG_CRITICAL); accountIdLineEdit->setFocus(); return; } if (pinCodeLineEdit->text().isEmpty()) { ((t_gui *)ui)->cb_show_msg(this, tr("Fill in your PIN code.").toStdString(), MSG_CRITICAL); pinCodeLineEdit->setFocus(); return; } QString profileName("Diamondcard-"); profileName.append(accountIdLineEdit->text()); QString filename(profileName); filename.append(USER_FILE_EXT); // Create a new user config while (!user_config->set_config(filename.toStdString())) { ((t_gui *)ui)->cb_show_msg(this, tr("A user profile with name %1 already exists.").arg(profileName).toStdString(), MSG_WARNING); // Ask user for a profile name GetProfileNameForm getProfileNameForm(this); getProfileNameForm.setModal(true); if (!getProfileNameForm.execNewName()) return; profileName = getProfileNameForm.getProfileName(); filename = profileName; filename.append(USER_FILE_EXT); } diamondcard_set_user_config(*user_config, nameLineEdit->text().toStdString(), accountIdLineEdit->text().toStdString(), pinCodeLineEdit->text().toStdString()); string error_msg; if (!user_config->write_config(user_config->get_filename(), error_msg)) { // Failed to write config file ((t_gui *)ui)->cb_show_msg(this, error_msg, MSG_CRITICAL); return; } emit newDiamondcardProfile(user_config->get_filename().c_str()); emit success(); accept(); } // Handle mouse clicks on labels. void DiamondcardProfileForm::mouseReleaseEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton && e->type() == QEvent::MouseButtonRelease) { processLeftMouseButtonRelease(e); } else { e->ignore(); } } void DiamondcardProfileForm::processLeftMouseButtonRelease(QMouseEvent *e) { if (signUpTextLabel->testAttribute(Qt::WA_UnderMouse)) { string url = diamondcard_url(DC_ACT_SIGNUP, "", ""); ((t_gui *)ui)->open_url_in_browser(url.c_str()); } else { e->ignore(); } } twinkle-1.10.1/src/gui/diamondcardprofileform.h000066400000000000000000000016651277565361200215330ustar00rootroot00000000000000#ifndef DIAMONDCARDPROFILEFORM_H #define DIAMONDCARDPROFILEFORM_H #include #include #include "user.h" #include "ui_diamondcardprofileform.h" class DiamondcardProfileForm : public QDialog, public Ui::DiamondcardProfileForm { Q_OBJECT public: DiamondcardProfileForm(QWidget* parent = 0); ~DiamondcardProfileForm(); virtual int exec( t_user * user ); public slots: virtual void destroyOldUserConfig(); virtual void show( t_user * user ); virtual void validate(); virtual void mouseReleaseEvent( QMouseEvent * e ); virtual void processLeftMouseButtonRelease( QMouseEvent * e ); signals: void success(); void newDiamondcardProfile(const QString&); protected slots: virtual void languageChange(); private: t_user *user_config; bool destroy_user_config; QLineEdit *accountIdLineEdit; QLineEdit *pinCodeLineEdit; QLineEdit *nameLineEdit; QLabel *signUpTextLabel; void init(); void destroy(); }; #endif twinkle-1.10.1/src/gui/diamondcardprofileform.ui000066400000000000000000000024171277565361200217150ustar00rootroot00000000000000 DiamondcardProfileForm 0 0 541 433 nameLineEdit accountIdLineEdit pinCodeLineEdit okPushButton cancelPushButton user.h QLabel QLineEdit okPushButton clicked() DiamondcardProfileForm validate() cancelPushButton clicked() DiamondcardProfileForm reject() twinkle-1.10.1/src/gui/dtmfform.cpp000066400000000000000000000104101277565361200171560ustar00rootroot00000000000000#include "dtmfform.h" #include #include /* * Constructs a DtmfForm which is a child of 'parent', with the * name 'name' and widget flags set to 'f' * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ DtmfForm::DtmfForm(QWidget* parent) : QDialog(parent) { setupUi(this); // Speed 40/40 (i.e. one tone every 80ms) m_insertTimer.setInterval(80); m_insertTimer.setSingleShot(false); connect(&m_insertTimer, SIGNAL(timeout()), this, SLOT(insertNextKey())); } /* * Destroys the object and frees any allocated resources */ DtmfForm::~DtmfForm() { // no need to delete child widgets, Qt does it all for us } void DtmfForm::dtmf1() { emit digits("1"); } void DtmfForm::dtmf2() { emit digits("2"); } void DtmfForm::dtmf3() { emit digits("3"); } void DtmfForm::dtmf4() { emit digits("4"); } void DtmfForm::dtmf5() { emit digits("5"); } void DtmfForm::dtmf6() { emit digits("6"); } void DtmfForm::dtmf7() { emit digits("7"); } void DtmfForm::dtmf8() { emit digits("8"); } void DtmfForm::dtmf9() { emit digits("9"); } void DtmfForm::dtmf0() { emit digits("0"); } void DtmfForm::dtmfStar() { emit digits("*"); } void DtmfForm::dtmfPound() { emit digits("#"); } void DtmfForm::dtmfA() { emit digits("A"); } void DtmfForm::dtmfB() { emit digits("B"); } void DtmfForm::dtmfC() { emit digits("C"); } void DtmfForm::dtmfD() { emit digits("D"); } void DtmfForm::keyPressEvent(QKeyEvent *e) { // DTMF keys if ((e->modifiers() & (Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier)) == Qt::NoModifier) { switch (e->key()) { case Qt::Key_1: onePushButton->animateClick(); break; case Qt::Key_2: case Qt::Key_A: case Qt::Key_B: case Qt::Key_C: twoPushButton->animateClick(); break; case Qt::Key_3: case Qt::Key_D: case Qt::Key_E: case Qt::Key_F: threePushButton->animateClick(); dtmf3(); break; case Qt::Key_4: case Qt::Key_G: case Qt::Key_H: case Qt::Key_I: fourPushButton->animateClick(); break; case Qt::Key_5: case Qt::Key_J: case Qt::Key_K: case Qt::Key_L: fivePushButton->animateClick(); break; case Qt::Key_6: case Qt::Key_M: case Qt::Key_N: case Qt::Key_O: sixPushButton->animateClick(); break; case Qt::Key_7: case Qt::Key_P: case Qt::Key_Q: case Qt::Key_R: case Qt::Key_S: sevenPushButton->animateClick(); break; case Qt::Key_8: case Qt::Key_T: case Qt::Key_U: case Qt::Key_V: eightPushButton->animateClick(); break; case Qt::Key_9: case Qt::Key_W: case Qt::Key_X: case Qt::Key_Y: case Qt::Key_Z: ninePushButton->animateClick(); break; case Qt::Key_0: case Qt::Key_Space: zeroPushButton->animateClick(); break; case Qt::Key_Asterisk: starPushButton->animateClick(); break; case Qt::Key_NumberSign: poundPushButton->animateClick(); break; default: e->ignore(); } } else if ((e->modifiers() == Qt::ShiftModifier && e->key() == Qt::Key_Insert) || (e->modifiers() == Qt::ControlModifier && e->key() == Qt::Key_V)) { // Insert from clipboard QClipboard *clipboard = QApplication::clipboard(); QString text = clipboard->text(); if (!text.isEmpty()) { m_remainingKeys = text; insertNextKey(); m_insertTimer.start(); } } } void DtmfForm::insertNextKey() { QChar key; bool keyValid; do { keyValid = true; key = m_remainingKeys[0].toLower(); m_remainingKeys = m_remainingKeys.mid(1); switch (key.toLatin1()) { case '0': zeroPushButton->animateClick(); break; case '1': onePushButton->animateClick(); break; case '2': twoPushButton->animateClick(); break; case '3': threePushButton->animateClick(); break; case '4': fourPushButton->animateClick(); break; case '5': fivePushButton->animateClick(); break; case '6' : sixPushButton->animateClick(); break; case '7': sevenPushButton->animateClick(); break; case '8': eightPushButton->animateClick(); break; case '9': ninePushButton->animateClick(); break; case '#': poundPushButton->animateClick(); break; case '*': starPushButton->animateClick(); break; default: keyValid = false; break; } } while (!keyValid); if (m_remainingKeys.isEmpty()) m_insertTimer.stop(); } twinkle-1.10.1/src/gui/dtmfform.h000066400000000000000000000014031277565361200166250ustar00rootroot00000000000000#ifndef DTMFFORM_H #define DTMFFORM_H #include #include #include #include "ui_dtmfform.h" class DtmfForm : public QDialog, private Ui::DtmfForm { Q_OBJECT public: explicit DtmfForm(QWidget *parent = 0); ~DtmfForm(); public slots: void dtmf1(); void dtmf2(); void dtmf3(); void dtmf4(); void dtmf5(); void dtmf6(); void dtmf7(); void dtmf8(); void dtmf9(); void dtmf0(); void dtmfStar(); void dtmfPound(); void dtmfA(); void dtmfB(); void dtmfC(); void dtmfD(); void insertNextKey(); protected: void keyPressEvent(QKeyEvent* e); signals: void digits(const QString&); private: QTimer m_insertTimer; QString m_remainingKeys; }; #endif // DTMFFORM_H twinkle-1.10.1/src/gui/dtmfform.ui000066400000000000000000000504371277565361200170260ustar00rootroot00000000000000 DtmfForm 0 0 350 302 0 0 Twinkle - DTMF Keypad 0 0 8 2 2 abc 0 0 3 3 def 0 0 194 202 210 194 202 210 194 202 210 Over decadic A. Normally not needed. A 0 0 4 4 ghi 0 0 5 5 jkl 0 0 6 6 mno 0 0 194 202 210 194 202 210 194 202 210 Over decadic B. Normally not needed. B 0 0 7 7 pqrs 0 0 8 8 tuv 0 0 9 9 wxyz 0 0 194 202 210 194 202 210 194 202 210 Over decadic C. Normally not needed. C 0 0 Star (*) * 0 0 0 0 0 0 Pound (#) # 0 0 194 202 210 194 202 210 194 202 210 Over decadic D. Normally not needed. D 0 0 1 1 true Qt::Horizontal QSizePolicy::Expanding 291 20 &Close Alt+C true onePushButton twoPushButton threePushButton aPushButton fourPushButton fivePushButton sixPushButton bPushButton sevenPushButton eightPushButton ninePushButton cPushButton starPushButton zeroPushButton poundPushButton dPushButton closePushButton closePushButton clicked() DtmfForm accept() 20 20 20 20 onePushButton clicked() DtmfForm dtmf1() 20 20 20 20 twoPushButton clicked() DtmfForm dtmf2() 20 20 20 20 threePushButton clicked() DtmfForm dtmf3() 20 20 20 20 fourPushButton clicked() DtmfForm dtmf4() 20 20 20 20 fivePushButton clicked() DtmfForm dtmf5() 20 20 20 20 sixPushButton clicked() DtmfForm dtmf6() 20 20 20 20 sevenPushButton clicked() DtmfForm dtmf7() 20 20 20 20 eightPushButton clicked() DtmfForm dtmf8() 20 20 20 20 ninePushButton clicked() DtmfForm dtmf9() 20 20 20 20 zeroPushButton clicked() DtmfForm dtmf0() 20 20 20 20 starPushButton clicked() DtmfForm dtmfStar() 20 20 20 20 poundPushButton clicked() DtmfForm dtmfPound() 20 20 20 20 aPushButton clicked() DtmfForm dtmfA() 20 20 20 20 bPushButton clicked() DtmfForm dtmfB() 20 20 20 20 cPushButton clicked() DtmfForm dtmfC() 20 20 20 20 dPushButton clicked() DtmfForm dtmfD() 20 20 20 20 twinkle-1.10.1/src/gui/getaddressform.cpp000066400000000000000000000162551277565361200203660ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "getaddressform.h" #include #include #include "sys_settings.h" #include "gui.h" #include "address_book.h" #include "addresstablemodel.h" #include "addresscardform.h" #include "audits/memman.h" #define TAB_KABC 0 #define TAB_LOCAL 1 #ifdef HAVE_KDE #include #include #include #include #include #define ABOOK ((KABC::AddressBook *)addrBook) // Column numbers #define AB_COL_NAME 0 #define AB_COL_PHONE 2 #endif GetAddressForm::GetAddressForm(QWidget *parent) : QDialog(parent) { setupUi(this); init(); m_model = new AddressTableModel(this, ab_local->get_address_list()); localListView->setModel(m_model); localListView->sortByColumn(COL_ADDR_NAME, Qt::AscendingOrder); #if QT_VERSION >= 0x050000 localListView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); #else localListView->horizontalHeader()->setResizeMode(QHeaderView::ResizeToContents); #endif } GetAddressForm::~GetAddressForm() { // destroy(); } void GetAddressForm::init() { #ifdef HAVE_KDE addrBook = (void *)KABC::StdAddressBook::self(false); loadAddresses(); connect(ABOOK, SIGNAL(addressBookChanged(AddressBook *)), this, SLOT(loadAddresses())); sipOnlyCheckBox->setChecked(sys_config->get_ab_show_sip_only()); #else addressTabWidget->setTabEnabled(addressTabWidget->indexOf(tabKABC), false); addressTabWidget->setCurrentIndex(TAB_LOCAL); #endif } void GetAddressForm::reload() { #ifdef HAVE_KDE ABOOK->disconnect(); KABC::StdAddressBook::close(); addrBook = (void *)KABC::StdAddressBook::self(false); loadAddresses(); connect(ABOOK, SIGNAL(addressBookChanged(AddressBook *)), this, SLOT(loadAddresses())); #endif } void GetAddressForm::show() { QDialog::show(); #ifdef HAVE_KDE if (addressListView->childCount() == 0) { if (localListView->childCount() == 0) { QMessageBox::information(this, PRODUCT_NAME, tr( "

" "You seem not to have any contacts with a phone number " "in KAddressBook, KDE's address book application. " "Twinkle retrieves all contacts with a phone number from " "KAddressBook. To manage your contacts you have to " "use KAddressBook." "

" "As an alternative you may use Twinkle's local address book." "

")); } else { addressTabWidget->setCurrentPage(TAB_LOCAL); } } #endif } void GetAddressForm::loadAddresses() { #ifdef HAVE_KDE // Explicit loading of address book is not needed as it is // automatically loaded. // if (!ABOOK->load()) return; addressListView->clear(); for (KABC::AddressBook::Iterator i = ABOOK->begin(); i != ABOOK->end(); i++) { KABC::PhoneNumber::List phoneNrs = i->phoneNumbers(); for (KABC::PhoneNumber::List::iterator j = phoneNrs.begin(); j != phoneNrs.end(); j++) { QString phone = (*j).number(); if (!sys_config->get_ab_show_sip_only() || phone.startsWith("sip:")) { new Q3ListViewItem(addressListView, i->realName(), (*j).typeLabel(), phone); } } } Q3ListViewItem *first = addressListView->firstChild(); if (first) addressListView->setSelected(first, true); #endif } void GetAddressForm::selectAddress() { if (addressTabWidget->currentIndex() == TAB_KABC) { selectKABCAddress(); } else { selectLocalAddress(); } } void GetAddressForm::selectKABCAddress() { #ifdef HAVE_KDE Q3ListViewItem *item = addressListView->selectedItem(); if (item) { QString name(item->text(AB_COL_NAME)); QString phone(item->text(AB_COL_PHONE)); phone = phone.stripWhiteSpace(); emit address(name, phone); // Signal display name and url combined. t_display_url du(t_url(phone.ascii()), name.ascii()); emit address(du.encode().c_str()); } accept(); #endif } void GetAddressForm::selectLocalAddress() { QModelIndexList sel = localListView->selectionModel()->selectedRows(); if (!sel.isEmpty()) { t_address_card card = m_model->getAddress(sel[0].row()); emit(QString::fromStdString(card.get_display_name()), QString::fromStdString(card.sip_address)); // Signal display name and url combined. t_display_url du(t_url(card.sip_address), card.get_display_name()); emit address(du.encode().c_str()); } accept(); } void GetAddressForm::toggleSipOnly(bool on) { #ifdef HAVE_KDE string msg; sys_config->set_ab_show_sip_only(on); // Ignore write failures. If for some reason the system config // could not be written, then this settings is lost after exiting Twinkle. // No need to bother the user at this point. (void)sys_config->write_config(msg); loadAddresses(); #endif } void GetAddressForm::addLocalAddress() { t_address_card card; AddressCardForm f(this); if (f.exec(card)) { ab_local->add_address(card); m_model->appendAddress(card); localListView->sortByColumn(localListView->horizontalHeader()->sortIndicatorSection(), localListView->horizontalHeader()->sortIndicatorOrder()); string error_msg; if (!ab_local->save(error_msg)) { ui->cb_show_msg(error_msg, MSG_CRITICAL); } } } void GetAddressForm::deleteLocalAddress() { QModelIndexList sel = localListView->selectionModel()->selectedRows(); if (sel.isEmpty()) return; t_address_card card = m_model->getAddress(sel[0].row()); QString card_name = QString::fromStdString(card.get_display_name()); QString msg = tr("Are you sure you want to delete contact '%1' from the local address book?").arg(card_name); QMessageBox *mb = new QMessageBox(tr("Delete contact"), msg, QMessageBox::Warning, QMessageBox::Yes, QMessageBox::No, QMessageBox::NoButton, this); MEMMAN_NEW(mb); if (mb->exec() == QMessageBox::Yes) { if (ab_local->del_address(card)) { m_model->removeAddress(sel[0].row()); string error_msg; if (!ab_local->save(error_msg)) { ui->cb_show_msg(error_msg, MSG_CRITICAL); } } } MEMMAN_DELETE(mb); delete mb; } void GetAddressForm::editLocalAddress() { QModelIndexList sel = localListView->selectionModel()->selectedRows(); if (sel.isEmpty()) return; t_address_card oldCard = m_model->getAddress(sel[0].row()); t_address_card newCard = oldCard; AddressCardForm f(this); if (f.exec(newCard)) { if (ab_local->update_address(oldCard, newCard)) { m_model->modifyAddress(sel[0].row(), newCard); localListView->sortByColumn(localListView->horizontalHeader()->sortIndicatorSection(), localListView->horizontalHeader()->sortIndicatorOrder()); string error_msg; if (!ab_local->save(error_msg)) { ui->cb_show_msg(error_msg, MSG_CRITICAL); } } } } twinkle-1.10.1/src/gui/getaddressform.h000066400000000000000000000013311277565361200200200ustar00rootroot00000000000000#ifndef GETADDRESSFORM_UI_H #define GETADDRESSFORM_UI_H #include "ui_getaddressform.h" #include "user.h" class AddressTableModel; class GetAddressForm : public QDialog, public Ui::GetAddressForm { Q_OBJECT public: GetAddressForm(QWidget *parent); ~GetAddressForm(); private: void init(); // void destroy(); public slots: void reload(); void show(); void loadAddresses(); void selectAddress(); void selectKABCAddress(); void selectLocalAddress(); void toggleSipOnly( bool on ); void addLocalAddress(); void deleteLocalAddress(); void editLocalAddress(); signals: void address(const QString &, const QString &); void address(const QString &); private: void *addrBook; AddressTableModel* m_model; }; #endif twinkle-1.10.1/src/gui/getaddressform.ui000066400000000000000000000273271277565361200202230ustar00rootroot00000000000000 GetAddressForm 0 0 655 474 Twinkle - Select address QTabWidget::Rounded 0 &KAddressBook This list of addresses is taken from <b>KAddressBook</b>. Contacts for which you did not provide a phone number are not shown here. To add, delete or modify address information you have to use KAddressBook. QAbstractItemView::SingleSelection QAbstractItemView::SelectRows 3 true true 1 false Check this option when you only want to see contacts with SIP addresses, i.e. starting with "<b>sip:</b>". &Show only SIP addresses Alt+S Qt::Horizontal QSizePolicy::Expanding 201 20 Reload the list of addresses from KAddressbook. &Reload Alt+R Qt::Horizontal QSizePolicy::Expanding 491 20 &Local address book Contacts in the local address book of Twinkle. QAbstractItemView::SingleSelection QAbstractItemView::SelectRows true 3 true true false Add a new contact to the local address book. &Add Alt+A Delete a contact from the local address book. &Delete Alt+D Edit a contact from the local address book. &Edit Alt+E Qt::Horizontal QSizePolicy::Expanding 161 20 Qt::Horizontal QSizePolicy::Expanding 378 20 &OK Alt+O true &Cancel Alt+C addressListView sipOnlyCheckBox reloadPushButton addressTabWidget localListView addPushButton deletePushButton editPushButton okPushButton cancelPushButton user.h okPushButton clicked() GetAddressForm selectAddress() 496 458 20 20 cancelPushButton clicked() GetAddressForm reject() 583 458 20 20 addressListView doubleClicked(QModelIndex) GetAddressForm selectKABCAddress() 33 61 20 20 sipOnlyCheckBox toggled(bool) GetAddressForm toggleSipOnly(bool) 34 365 20 20 reloadPushButton clicked() GetAddressForm reload() 34 392 20 20 addPushButton clicked() GetAddressForm addLocalAddress() 34 393 20 20 deletePushButton clicked() GetAddressForm deleteLocalAddress() 121 393 20 20 editPushButton clicked() GetAddressForm editLocalAddress() 208 393 20 20 localListView doubleClicked(QModelIndex) GetAddressForm selectLocalAddress() 33 61 20 20 twinkle-1.10.1/src/gui/getprofilenameform.cpp000066400000000000000000000035641277565361200212410ustar00rootroot00000000000000#include "getprofilenameform.h" #include #include #include "user.h" #include "protocol.h" /* * Constructs a GetProfileNameForm which is a child of 'parent', with the * name 'name' and widget flags set to 'f' * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ GetProfileNameForm::GetProfileNameForm(QWidget* parent) : QDialog(parent) { setupUi(this); } /* * Destroys the object and frees any allocated resources */ GetProfileNameForm::~GetProfileNameForm() { // no need to delete child widgets, Qt does it all for us } void GetProfileNameForm::init() { // Letters, digits, underscore, minus QRegExp rxFilenameChars("[\\w\\-][\\w\\-@\\.]*"); // Set validators // USER profileLineEdit->setValidator(new QRegExpValidator(rxFilenameChars, this)); } void GetProfileNameForm::validate() { if (profileLineEdit->text().isEmpty()) return; // Find the .twinkle directory in HOME QDir d = QDir::home(); if (!d.cd(USER_DIR)) { QMessageBox::critical(this, PRODUCT_NAME, tr("Cannot find .twinkle directory in your home directory.")); reject(); } QString filename = profileLineEdit->text(); filename.append(USER_FILE_EXT); QString fullname = d.filePath(filename); if (QFile::exists(fullname)) { QMessageBox::warning(this, PRODUCT_NAME, tr("Profile already exists.")); return; } accept(); } QString GetProfileNameForm:: getProfileName() { return profileLineEdit->text(); } // Execute a dialog to get a name for a new profile int GetProfileNameForm::execNewName() { profileTextLabel->setText(tr("Enter a name for your profile:")); return exec(); } // Execute this dialog to get a new name for an existing profile int GetProfileNameForm::execRename(const QString &oldName) { QString s = tr("Rename profile '%1' to:").arg(oldName); profileTextLabel->setText(s); return exec(); } twinkle-1.10.1/src/gui/getprofilenameform.h000066400000000000000000000007431277565361200207020ustar00rootroot00000000000000#ifndef GETPROFILENAMEFORM_H #define GETPROFILENAMEFORM_H #include #include "ui_getprofilenameform.h" class GetProfileNameForm : public QDialog, private Ui::GetProfileNameForm { Q_OBJECT public: GetProfileNameForm(QWidget* parent = 0); ~GetProfileNameForm(); QString getProfileName(); int execNewName(); int execRename( const QString & oldName ); public slots: void validate(); private: void init(); }; #endif // GETPROFILENAMEFORM_H twinkle-1.10.1/src/gui/getprofilenameform.ui000066400000000000000000000107521277565361200210710ustar00rootroot00000000000000 GetProfileNameForm 0 0 430 127 Twinkle - Profile name :/icons/images/penguin_big.png false 81 20 QSizePolicy::Expanding Qt::Horizontal &OK true &Cancel 5 0 0 0 Enter a name for your profile: false Qt::AlignVCenter false <b>The name of your profile</b> <br><br> A profile contains your user settings, e.g. your user name and password. You have to give each profile a name. <br><br> If you have multiple SIP accounts, you can create multiple profiles. When you startup Twinkle it will show you the list of profile names from which you can select the profile you want to run. <br><br> To remember your profiles easily you could use your SIP user name as a profile name, e.g. <b>example@example.com</b> profileLineEdit okPushButton cancelPushButton cancelPushButton clicked() GetProfileNameForm reject() okPushButton clicked() GetProfileNameForm validate() twinkle-1.10.1/src/gui/gui.cpp000066400000000000000000002445471277565361200161500ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "twinkle_config.h" #include #include #include #include #ifdef HAVE_KDE #include #include #include #include #endif // This include is needed to avoid build error when building Twinkle // without Qt. One of the other includes below seems to include qdir.h // indirectly. But by that time it probably conflicts with a macro causing // compilation errors reported in qdir.h Include qdir.h here avoids the // conflict. #include "qdir.h" #include "gui.h" #include "line.h" #include "log.h" #include "sys_settings.h" #include "user.h" #include "cmd_socket.h" #include "audio/rtp_telephone_event.h" #include "sockets/interfaces.h" #include "threads/thread.h" #include "audits/memman.h" #include "authenticationform.h" #include "mphoneform.h" #include "selectnicform.h" #include "selectprofileform.h" #include "messageformview.h" #include "util.h" #include "address_finder.h" #include "yesnodialog.h" #include "command_args.h" #include "im/msg_session.h" #include "qcombobox.h" #include "qlabel.h" #include "qlayout.h" #include "qmessagebox.h" #include "qpixmap.h" #include #include "qpushbutton.h" #include "qsize.h" #include "qsizepolicy.h" #include "qstring.h" #include "qtextcodec.h" #include "qtooltip.h" #include extern string user_host; extern pthread_t thread_id_main; // External command arguments extern t_command_args g_cmd_args; extern QSettings* g_gui_state; QString str2html(const QString &s) { QString result(s); result.replace('&', "&"); result.replace('<', "<"); result.replace('>', ">"); return result; } void setDisabledIcon(QAction *action, const QString &icon) { QIcon i = action->icon(); i.addPixmap(QPixmap(icon), QIcon::Disabled); action->setIcon(i); } void setDisabledIcon(QToolButton *toolButton, const QString &icon) { QIcon i = toolButton->icon(); i.addPixmap(QPixmap(icon), QIcon::Disabled); toolButton->setIcon(i); } ///////////////////////////////////////////////// // PRIVATE ///////////////////////////////////////////////// void t_gui::setLineFields(int line) { if (line == 0) { fromLabel = mainWindow->from1Label; toLabel = mainWindow->to1Label; subjectLabel = mainWindow->subject1Label; codecLabel = mainWindow->codec1TextLabel; photoLabel = mainWindow->photo1Label; } else { fromLabel = mainWindow->from2Label; toLabel = mainWindow->to2Label; subjectLabel = mainWindow->subject2Label; codecLabel = mainWindow->codec2TextLabel; photoLabel = mainWindow->photo2Label; } } void t_gui::clearLineFields(int line) { if (line >= NUM_USER_LINES) return; setLineFields(line); fromLabel->clear(); fromLabel->setToolTip(QString()); toLabel->clear(); toLabel->setToolTip(QString()); subjectLabel->clear(); subjectLabel->setToolTip(QString()); codecLabel->clear(); photoLabel->clear(); photoLabel->hide(); } void t_gui::displayTo(const QString &s) { toLabel->setText(s); toLabel->setCursorPosition(0); toLabel->setToolTip(s); } void t_gui::displayFrom(const QString &s) { fromLabel->setText(s); fromLabel->setCursorPosition(0); fromLabel->setToolTip(s); } void t_gui::displaySubject(const QString &s) { subjectLabel->setText(s); subjectLabel->setCursorPosition(0); subjectLabel->setToolTip(s); } void t_gui::displayCodecInfo(int line) { if (line > NUM_USER_LINES) return; setLineFields(line); codecLabel->clear(); t_call_info call_info = phone->get_call_info(line); if (call_info.send_codec == CODEC_NULL && call_info.recv_codec == CODEC_NULL) { return; } if (call_info.send_codec == CODEC_NULL) { codecLabel->setText(format_codec(call_info.recv_codec).c_str()); return; } if (call_info.recv_codec == CODEC_NULL) { codecLabel->setText(format_codec(call_info.send_codec).c_str()); return; } if (call_info.send_codec == call_info.recv_codec) { codecLabel->setText(format_codec(call_info.send_codec).c_str()); return; } QString s = format_codec(call_info.send_codec).c_str(); s.append('/').append(format_codec(call_info.recv_codec).c_str()); codecLabel->setText(s); } void t_gui::displayPhoto(const QImage &photo) { if (mainWindow->getViewCompactLineStatus()) { // In compact line status mode, no photo can be shown return; } if (photo.isNull()) { photoLabel->hide(); } else { QPixmap pm; pm.convertFromImage(photo.scaled( QSize(photoLabel->width(), photoLabel->height()), Qt::KeepAspectRatio, Qt::SmoothTransformation)); photoLabel->setPixmap(pm); photoLabel->show(); } } ///////////////////////////////////////////////// // PROTECTED ///////////////////////////////////////////////// bool t_gui::do_invite(const string &destination, const string &display, const string &subject, bool immediate, bool anonymous) { QMetaObject::invokeMethod(this, "gui_do_invite", Q_ARG(QString, QString::fromStdString(destination)), Q_ARG(QString, QString::fromStdString(display)), Q_ARG(QString, QString::fromStdString(subject)), Q_ARG(bool, immediate), Q_ARG(bool, anonymous)); return true; } void t_gui::do_redial(void) { QMetaObject::invokeMethod(this, "gui_do_redial"); } void t_gui::do_answer(void) { QMetaObject::invokeMethod(this, "gui_do_answer"); } void t_gui::do_answerbye(void) { QMetaObject::invokeMethod(this, "gui_do_answerbye"); } void t_gui::do_reject(void) { QMetaObject::invokeMethod(this, "gui_do_reject"); } void t_gui::do_redirect(bool show_status, bool type_present, t_cf_type cf_type, bool action_present, bool enable, int num_redirections, const list &dest_strlist, bool immediate) { if (show_status) { // Show status not supported in GUI return; } QMetaObject::invokeMethod(this, "gui_do_redirect", Qt::BlockingQueuedConnection, Q_ARG(bool, type_present), Q_ARG(t_cf_type, cf_type), Q_ARG(bool, action_present), Q_ARG(bool, enable), Q_ARG(int, num_redirections), Q_ARG(std::list, dest_strlist), Q_ARG(bool, immediate)); } void t_gui::do_dnd(bool show_status, bool toggle, bool enable) { if (show_status) { // Show status not supported in GUI return; } QMetaObject::invokeMethod(this, "gui_do_dnd", Q_ARG(bool, toggle), Q_ARG(bool, enable)); } void t_gui::do_auto_answer(bool show_status, bool toggle, bool enable) { if (show_status) { // Show status not supported in GUI return; } QMetaObject::invokeMethod(this, "gui_do_auto_answer", Q_ARG(bool, toggle), Q_ARG(bool, enable)); } void t_gui::do_bye(void) { QMetaObject::invokeMethod(this, "gui_do_bye"); } void t_gui::do_hold(void) { QMetaObject::invokeMethod(this, "gui_do_hold"); } void t_gui::do_retrieve(void) { QMetaObject::invokeMethod(this, "gui_do_retrieve"); } bool t_gui::do_refer(const string &destination, t_transfer_type transfer_type, bool immediate) { QMetaObject::invokeMethod(this, "gui_do_refer", Q_ARG(QString, QString::fromStdString(destination)), Q_ARG(t_transfer_type, transfer_type), Q_ARG(bool, immediate)); return true; } void t_gui::do_conference(void) { QMetaObject::invokeMethod(this, "gui_do_conference"); } void t_gui::do_mute(bool show_status, bool toggle, bool enable) { if (show_status) { // Show status not supported in GUI return; } QMetaObject::invokeMethod(this, "gui_do_mute", Q_ARG(bool, toggle), Q_ARG(bool, enable)); } void t_gui::do_dtmf(const string &digits) { QMetaObject::invokeMethod(this, "gui_do_dtmf", Q_ARG(QString, QString::fromStdString(digits))); } void t_gui::do_register(bool reg_all_profiles) { list l; if (reg_all_profiles) { l = phone->ref_users(); } else { QString profile; t_user *user; QMetaObject::invokeMethod(this, "gui_get_current_profile", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, profile)); user = phone->ref_user_profile(profile.toStdString()); l.push_back(user); } mainWindow->do_phoneRegister(l); } void t_gui::do_deregister(bool dereg_all_profiles, bool dereg_all_devices) { list l; if (dereg_all_profiles) { l = phone->ref_users(); } else { QString profile; t_user *user; QMetaObject::invokeMethod(this, "gui_get_current_profile", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, profile)); user = phone->ref_user_profile(profile.toStdString()); l.push_back(user); } if (dereg_all_devices) { mainWindow->do_phoneDeregisterAll(l); } else { mainWindow->do_phoneDeregister(l); } } void t_gui::do_fetch_registrations(void) { QMetaObject::invokeMethod(mainWindow, "phoneShowRegistrations"); } bool t_gui::do_options(bool dest_set, const string &destination, bool immediate) { // In-dialog OPTIONS request int line = phone->get_active_line(); if (phone->get_line_substate(line) == LSSUB_ESTABLISHED) { ((t_gui *)ui)->action_options(); return true; } if (immediate) { QString profile; t_user *user; QMetaObject::invokeMethod(this, "gui_get_current_profile", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, profile)); user = phone->ref_user_profile(profile.toStdString()); t_url dst_url(expand_destination(user, destination)); if (dst_url.is_valid()) { mainWindow->do_phoneTermCap(user, dst_url); } } else { QMetaObject::invokeMethod(mainWindow, "phoneTermCap", Q_ARG(QString, QString::fromStdString(destination))); } return true; } void t_gui::do_line(int line) { // Cannot get current line number via CLI interface on GUI. // So return in this case. if (line == 0) return; phone->pub_activate_line(line - 1); } void t_gui::do_user(const string &profile_name) { QMetaObject::invokeMethod(this, "gui_do_user", Q_ARG(QString, QString::fromStdString(profile_name))); } QString t_gui::gui_get_current_profile() { return mainWindow->userComboBox->currentText(); } bool t_gui::do_message(const string &destination, const string &display, const im::t_msg &msg) { t_user *user; QString profile; QMetaObject::invokeMethod(this, "gui_get_current_profile", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, profile)); user = phone->ref_user_profile(profile.toStdString()); t_url dest_url(expand_destination(user, destination)); if (dest_url.is_valid()) { phone->pub_send_message(user, dest_url, display, msg); } return true; } void t_gui::do_presence(t_presence_state::t_basic_state basic_state) { QString profile; t_user *user; QMetaObject::invokeMethod(this, "gui_get_current_profile", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, profile)); user = phone->ref_user_profile(profile.toStdString()); phone->pub_publish_presence(user, basic_state); } void t_gui::do_zrtp(t_zrtp_cmd zrtp_cmd) { lock(); switch (zrtp_cmd) { case ZRTP_ENCRYPT: QMetaObject::invokeMethod(mainWindow, "phoneEnableZrtp", Q_ARG(bool, true)); break; case ZRTP_GO_CLEAR: QMetaObject::invokeMethod(mainWindow, "phoneEnableZrtp", Q_ARG(bool, false)); break; case ZRTP_CONFIRM_SAS: QMetaObject::invokeMethod(mainWindow, "phoneConfirmZrtpSas"); break; case ZRTP_RESET_SAS: QMetaObject::invokeMethod(mainWindow, "phoneResetZrtpSasConfirmation"); break; default: assert(false); } unlock(); } void t_gui::do_quit(void) { QMetaObject::invokeMethod(mainWindow, "fileExit"); } void t_gui::do_help(const list &al) { // Nothing to do in GUI mode return; } void t_gui::gui_do_invite(const QString &destination, const QString &display, const QString &subject, bool immediate, bool anonymous) { if (mainWindow->callInvite->isEnabled()) { if (immediate) { t_user *user = phone->ref_user_profile( mainWindow->userComboBox->currentText().toStdString()); t_url dst_url(expand_destination(user, destination.toStdString())); if (dst_url.is_valid()) { mainWindow->do_phoneInvite(user, display, dst_url, subject, anonymous); } } else { t_url dest_url(destination.toStdString()); t_display_url du(dest_url, display.toStdString()); mainWindow->phoneInvite(du.encode().c_str(), subject, anonymous); } } } void t_gui::gui_do_redial(void) { if (mainWindow->callRedial->isEnabled()) { mainWindow->phoneRedial(); } } void t_gui::gui_do_answer(void) { if (mainWindow->callAnswer->isEnabled()) { mainWindow->phoneAnswer(); } } void t_gui::gui_do_answerbye(void) { if (mainWindow->callAnswer->isEnabled()) { mainWindow->phoneAnswer(); } else if (mainWindow->callBye->isEnabled()) { mainWindow->phoneBye(); } } void t_gui::gui_do_reject(void) { if (mainWindow->callReject->isEnabled()) { mainWindow->phoneReject(); } } void t_gui::gui_do_redirect(bool type_present, t_cf_type cf_type, bool action_present, bool enable, int num_redirections, const list &dest_strlist, bool immediate) { t_user *user = phone->ref_user_profile(mainWindow-> userComboBox->currentText().toStdString()); list dest_list; for (list::const_iterator i = dest_strlist.begin(); i != dest_strlist.end(); i++) { t_display_url du; du.url = expand_destination(user, *i); du.display.clear(); if (!du.is_valid()) return; dest_list.push_back(du); } // Enable/disable permanent redirections if (type_present) { if (enable) { phone->ref_service(user)->enable_cf(cf_type, dest_list); } else { phone->ref_service(user)->disable_cf(cf_type); } mainWindow->updateServicesStatus(); return; } else { if (action_present) { if (!enable) { phone->ref_service(user)->disable_cf(CF_ALWAYS); phone->ref_service(user)->disable_cf(CF_BUSY); phone->ref_service(user)->disable_cf(CF_NOANSWER); mainWindow->updateServicesStatus(); } return; } } if (mainWindow->callRedirect->isEnabled()) { if (immediate) { mainWindow->do_phoneRedirect(dest_list); } else { mainWindow->phoneRedirect(dest_strlist); } } } void t_gui::gui_do_dnd(bool toggle, bool enable) { if (phone->ref_users().size() == 1) { if (toggle) { enable = !mainWindow->serviceDnd->isChecked(); } mainWindow->srvDnd(enable); mainWindow->serviceDnd->setChecked(enable); } else { t_user *user = phone->ref_user_profile(mainWindow-> userComboBox->currentText().toStdString()); list l; l.push_back(user); if (toggle) { enable = !phone->ref_service(user)->is_dnd_active(); } if (enable) { mainWindow->do_srvDnd_enable(l); } else { mainWindow->do_srvDnd_disable(l); } } } void t_gui::gui_do_auto_answer(bool toggle, bool enable) { if (phone->ref_users().size() == 1) { if (toggle) { enable = !mainWindow->serviceAutoAnswer->isChecked(); } mainWindow->srvAutoAnswer(enable); mainWindow->serviceAutoAnswer->setChecked(enable); } else { t_user *user = phone->ref_user_profile(mainWindow-> userComboBox->currentText().toStdString()); list l; l.push_back(user); if (toggle) { enable = !phone->ref_service(user)-> is_auto_answer_active(); } if (enable) { mainWindow->do_srvAutoAnswer_enable(l); } else { mainWindow->do_srvAutoAnswer_disable(l); } } } void t_gui::gui_do_bye(void) { if (mainWindow->callBye->isEnabled()) { mainWindow->phoneBye(); } } void t_gui::gui_do_hold(void) { if (mainWindow->callHold->isEnabled() && !mainWindow->callHold->isChecked()) { mainWindow->phoneHold(true); } } void t_gui::gui_do_retrieve(void) { if (mainWindow->callHold->isEnabled() && mainWindow->callHold->isChecked()) { mainWindow->phoneHold(false); } } void t_gui::gui_do_refer(const QString &destination, t_transfer_type transfer_type, bool immediate) { if (mainWindow->callTransfer->isEnabled() && !mainWindow->callTransfer->isChecked()) { if (immediate) { t_display_url du; t_user *user = phone->ref_user_profile(mainWindow-> userComboBox->currentText().toStdString()); du.url = expand_destination(user, destination.toStdString()); if (du.is_valid() || transfer_type == TRANSFER_OTHER_LINE) { mainWindow->do_phoneTransfer(du, transfer_type); } } else { mainWindow->phoneTransfer(destination.toStdString(), transfer_type); } } else if(mainWindow->callTransfer->isEnabled() && mainWindow->callTransfer->isChecked()) { if (transfer_type != TRANSFER_CONSULT) { mainWindow->do_phoneTransferLine(); } } } void t_gui::gui_do_conference(void) { if (mainWindow->callConference->isEnabled()) { mainWindow->phoneConference(); } } void t_gui::gui_do_mute(bool toggle, bool enable) { if (mainWindow->callMute->isEnabled()) { if (toggle) enable = !phone->is_line_muted(phone->get_active_line()); mainWindow->phoneMute(enable); } } void t_gui::gui_do_dtmf(const QString &digits) { if (mainWindow->callDTMF->isEnabled()) { mainWindow->sendDTMF(digits); } } void t_gui::gui_do_user(const QString &profile_name) { for (int i = 0; i < mainWindow->userComboBox->count(); i++) { if (mainWindow->userComboBox->itemText(i) == profile_name) { mainWindow->userComboBox->setCurrentIndex(i); break; } } } void t_gui::gui_cmd_call(const string &destination, bool immediate) { string subject; string dst_no_headers; t_display_url du; t_user *user = phone->ref_user_profile( mainWindow->userComboBox->currentText().toStdString()); expand_destination(user, destination, du, subject, dst_no_headers); if (!du.is_valid()) return; if (immediate) { mainWindow->do_phoneInvite(user, du.display.c_str(), du.url, subject.c_str(), false); } else { mainWindow->phoneInvite(dst_no_headers.c_str(), subject.c_str(), false); } } void t_gui::gui_cmd_show(void) { if (mainWindow->isMinimized()) { mainWindow->setWindowState((mainWindow->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); mainWindow->raise(); } else { mainWindow->show(); mainWindow->raise(); mainWindow->activateWindow(); } } void t_gui::gui_cmd_hide(void) { if (sys_config->get_gui_use_systray()) { mainWindow->hide(); } else { mainWindow->setWindowState(mainWindow->windowState() | Qt::WindowMinimized); } } ///////////////////////////////////////////////// // PUBLIC ///////////////////////////////////////////////// t_gui::t_gui(t_phone *_phone) : t_userintf(_phone), timerUpdateMessageSessions(NULL) { use_stdout = false; lastFileBrowsePath = DIR_HOME; qRegisterMetaType("t_user*"); qRegisterMetaType("t_register_type"); qRegisterMetaType("t_transfer_type"); qRegisterMetaType("t_cf_type"); qRegisterMetaType("string"); qRegisterMetaType>("std::list"); mainWindow = new MphoneForm; #ifdef HAVE_KDE sys_tray_popup = NULL; #endif for (int i = 0; i < NUM_USER_LINES; i++) { QObject::connect(&autoShowTimer[i], SIGNAL(timeout()), mainWindow, SLOT(show())); } MEMMAN_NEW(mainWindow); connect(this, SIGNAL(update_reg_status()), mainWindow, SLOT(updateRegStatus()), Qt::QueuedConnection); connect(this, SIGNAL(update_mwi()), mainWindow, SLOT(updateMwi()), Qt::QueuedConnection); connect(this, SIGNAL(update_state()), mainWindow, SLOT(updateState()), Qt::QueuedConnection); connect(this, SIGNAL(mw_display(const QString&)), mainWindow, SLOT(display(const QString&)), Qt::QueuedConnection); connect(this, SIGNAL(mw_display_header()), mainWindow, SLOT(displayHeader()), Qt::QueuedConnection); connect(this, SIGNAL(mw_update_log(bool)), mainWindow, SLOT(updateLog(bool)), Qt::QueuedConnection); connect(this, SIGNAL(mw_update_call_history()), mainWindow, SLOT(updateCallHistory()), Qt::QueuedConnection); connect(this, SIGNAL(mw_update_missed_call_status(int)), mainWindow, SLOT(updateMissedCallStatus(int)), Qt::QueuedConnection); } t_gui::~t_gui() { destroyAllMessageSessions(); MEMMAN_DELETE(mainWindow); delete mainWindow; } void t_gui::run(void) { // Start asynchronous event processor thr_process_events = new t_thread(process_events_main, NULL); MEMMAN_NEW(thr_process_events); QString s; list user_list = phone->ref_users(); // The Qt event loop is not running yet. Explicitly take the Qt lock // to avoid race conditions with other threads that may call GUI call // backs. // NOTE: the t_gui::lock() method cannot be used as this method // will not lock from the main thread and we are running in the // main thread (a bit of a kludge). //qApp->lock(); // Set configuration file name in titlebar s = PRODUCT_NAME; mainWindow->setWindowTitle(s); // Set user combo box mainWindow->updateUserComboBox(); // Display product information s = PRODUCT_NAME; s.append(' ').append(PRODUCT_VERSION).append(", "); s.append(sys_config->get_product_date().c_str()); mainWindow->display(s); s = "Copyright (C) 2005-2015 "; s.append(PRODUCT_AUTHOR); mainWindow->display(s); // Restore user interface state from previous session restore_state(); // Initialize phone functions run_on_event_queue([=]() { phone->init(); }); // Set controls in correct status mainWindow->updateState(); mainWindow->updateRegStatus(); mainWindow->updateMwi(); mainWindow->updateServicesStatus(); mainWindow->updateMissedCallStatus(0); mainWindow->updateMenuStatus(); // Clear line field info fields clearLineFields(0); clearLineFields(1); // Populate buddy list mainWindow->populateBuddyList(); // Set width of window to width of tool bar // int widthToolBar = mainWindow->callToolbar->width(); // QSize sizeMainWin = mainWindow->size(); // sizeMainWin.setWidth(widthToolBar); // mainWindow->resize(sizeMainWin); // Start QApplication/KApplication if (qApp->isSessionRestored() && sys_config->get_ui_session_id() == qApp->sessionId().toStdString()) { // Restore previous session restore_session_state(); } else { if ((sys_config->get_start_hidden() && !g_cmd_args.cmd_show) || g_cmd_args.cmd_hide) { mainWindow->hide(); } else { mainWindow->show(); } } // Activate a profile if the --set-profile option was given on the command // line. if (!g_cmd_args.cmd_set_profile.isEmpty()) { cmdsocket::cmd_cli(string("user ") + g_cmd_args.cmd_set_profile.toStdString(), true); } // Execute the call command if a callto destination was specified on the // command line if (!g_cmd_args.callto_destination.isEmpty()) { cmdsocket::cmd_call(g_cmd_args.callto_destination.toStdString(), g_cmd_args.cmd_immediate_mode); } // Execute a CLI command if one was given on the command line if (!g_cmd_args.cli_command.isEmpty()) { cmdsocket::cmd_cli(g_cmd_args.cli_command.toStdString(), g_cmd_args.cmd_immediate_mode); } //qApp->unlock(); // Start Qt application qApp->exec(); // Terminate phone functions phone->terminate(); // Make user interface state persistent save_state(); } void t_gui::save_state(void) { lock(); sys_config->set_last_used_profile( mainWindow->userComboBox->currentText().toStdString()); list history; for (int i = 0; i < mainWindow->callComboBox->count(); i++) { history.push_back(mainWindow->callComboBox->itemText(i).toStdString()); } sys_config->set_dial_history(history); sys_config->set_show_display(mainWindow->getViewDisplay()); sys_config->set_compact_line_status(mainWindow->getViewCompactLineStatus()); sys_config->set_show_buddy_list(mainWindow->getViewBuddyList()); t_userintf::save_state(); unlock(); } void t_gui::restore_state(void) { lock(); // The last used profile is selected when the userComboBox is // filled by MphoneForm::updateUserComboBox mainWindow->callComboBox->clear(); list dial_history = sys_config->get_dial_history(); for (list::reverse_iterator i = dial_history.rbegin(); i != dial_history.rend(); i++) { mainWindow->addToCallComboBox(i->c_str()); } mainWindow->showDisplay(sys_config->get_show_display()); mainWindow->showCompactLineStatus(sys_config->get_compact_line_status()); mainWindow->showBuddyList(sys_config->get_show_buddy_list()); t_userintf::restore_state(); unlock(); } void t_gui::save_session_state(void) { lock(); t_win_geometry geometry(mainWindow->x(), mainWindow->y(), mainWindow->width(), mainWindow->height()); sys_config->set_ui_session_main_geometry(geometry); sys_config->set_ui_session_main_state(mainWindow->windowState()); sys_config->set_ui_session_main_hidden(mainWindow->isHidden()); unlock(); } void t_gui::restore_session_state(void) { lock(); t_win_geometry geometry = sys_config->get_ui_session_main_geometry(); mainWindow->setGeometry(geometry.x, geometry.y, geometry.width, geometry.height); mainWindow->setWindowState(Qt::WindowStates(sys_config->get_ui_session_main_state())); mainWindow->setHidden(sys_config->get_ui_session_main_hidden()); unlock(); } void t_gui::lock(void) { // To synchronize actions on the Qt widget the application lock // is used. The main thread running the Qt event loop takes the // application lock itself already. So take the lock if this is not the // main thread. // If the Qt event loop has not been started yet, then the lock // should also be taken from the main thread. t_userintf::lock(); //if (!t_thread::is_self(thread_id_main)) { // qApp->lock(); //} } void t_gui::unlock(void) { t_userintf::lock(); //if (!t_thread::is_self(thread_id_main)) { // qApp->unlock(); //} } string t_gui::select_network_intf(void) { string ip; list *l = get_interfaces(); // The socket routines are not under control of MEMMAN so report // the allocation here. MEMMAN_NEW(l); if (l->size() == 0) { cb_show_msg(qApp->translate("GUI", "Cannot find a network interface. Twinkle will use " "127.0.0.1 as the local IP address. When you connect to " "the network you have to restart Twinkle to use the correct " "IP address.").toStdString(), MSG_WARNING); MEMMAN_DELETE(l); delete l; return "127.0.0.1"; } if (l->size() == 1) { // There is only 1 interface ip = l->front().get_ip_addr(); } else { // There are multiple interfaces SelectNicForm *sf = new SelectNicForm(NULL); sf->setModal(true); MEMMAN_NEW(sf); QString item; for (list::iterator i = l->begin(); i != l->end(); i++) { item = i->name.c_str(); item.append(':').append(i->get_ip_addr().c_str()); new QListWidgetItem(QPixmap(":/icons/images/kcmpci16.png"), item, sf->nicListBox); } sf->nicListBox->setCurrentItem(0); sf->exec(); int selection = sf->nicListBox->currentRow(); int num = 0; for (list::iterator i = l->begin(); i != l->end(); i++) { if (num == selection) { ip = i->get_ip_addr(); break; } num++; } MEMMAN_DELETE(sf); delete sf; } MEMMAN_DELETE(l); delete l; return ip; } bool t_gui::select_user_config(list &config_files) { SelectProfileForm f(nullptr); f.setModal(true); if (f.execForm()) { config_files = f.selectedProfiles; return true; } return false; } // GUI call back functions void t_gui::cb_incoming_call(t_user *user_config, int line, const t_request *r) { if (line >= NUM_USER_LINES) return; lock(); QString s; setLineFields(line); // Incoming call for to-header emit mw_display_header(); s = qApp->translate("GUI", "Line %1: incoming call for %2").arg(line + 1).arg( format_sip_address(user_config, r->hdr_to.display, r->hdr_to.uri).c_str()); emit mw_display(s); // Is this a transferred call? QString referredByParty; if (r->hdr_referred_by.is_populated()) { referredByParty = format_sip_address(user_config, r->hdr_referred_by.display, r->hdr_referred_by.uri).c_str(); s = "Call transferred by "; s = qApp->translate("GUI", "Call transferred by %1").arg(referredByParty); emit mw_display(s); } // From QString fromParty = format_sip_address(user_config, r->hdr_from.get_display_presentation(), r->hdr_from.uri).c_str(); s = fromParty; QString organization(""); if (r->hdr_organization.is_populated()) { organization = r->hdr_organization.name.c_str(); s.append(", ").append(organization); } displayFrom(s); // Display photo QImage fromPhoto; if (sys_config->get_ab_lookup_photo()) { t_address_finder *af = t_address_finder::get_instance(); fromPhoto = af->find_photo(user_config, r->hdr_from.uri); } displayPhoto(fromPhoto); // To s = ""; s.append(format_sip_address(user_config, r->hdr_to.display, r->hdr_to.uri).c_str()); displayTo(s); // Subject QString subject(""); if (r->hdr_subject.is_populated()) { subject = r->hdr_subject.subject.c_str(); } displaySubject(subject); cb_notify_call(line, fromParty, organization, fromPhoto, subject, referredByParty); unlock(); } void t_gui::cb_call_cancelled(int line) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: far end cancelled call.").arg(line + 1); emit mw_display(s); cb_stop_call_notification(line); unlock(); } void t_gui::cb_far_end_hung_up(int line) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: far end released call.").arg(line + 1); emit mw_display(s); cb_stop_call_notification(line); unlock(); } void t_gui::cb_answer_timeout(int line) { if (line >= NUM_USER_LINES) return; lock(); cb_stop_call_notification(line); unlock(); } void t_gui::cb_sdp_answer_not_supported(int line, const string &reason) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: SDP answer from far end not supported.").arg(line + 1); emit mw_display(s); s = reason.c_str(); emit mw_display(s); cb_stop_call_notification(line); unlock(); } void t_gui::cb_sdp_answer_missing(int line) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: SDP answer from far end missing.").arg(line + 1); emit mw_display(s); cb_stop_call_notification(line); unlock(); } void t_gui::cb_unsupported_content_type(int line, const t_sip_message *r) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: Unsupported content type in answer from far end.").arg(line + 1); emit mw_display(s); s = r->hdr_content_type.media.type.c_str(); s.append("/").append(r->hdr_content_type.media.subtype.c_str()); emit mw_display(s); cb_stop_call_notification(line); unlock(); } void t_gui::cb_ack_timeout(int line) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: no ACK received, call will be terminated.").arg(line + 1); emit mw_display(s); cb_stop_call_notification(line); unlock(); } void t_gui::cb_100rel_timeout(int line) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: no PRACK received, call will be terminated.").arg(line + 1); emit mw_display(s); cb_stop_call_notification(line); unlock(); } void t_gui::cb_prack_failed(int line, const t_response *r) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: PRACK failed.").arg(line + 1); emit mw_display(s); s = QString().setNum(r->code); s.append(' ').append(r->reason.c_str()); emit mw_display(s); cb_stop_call_notification(line); unlock(); } void t_gui::cb_provisional_resp_invite(int line, const t_response *r) { if (line >= NUM_USER_LINES) return; lock(); emit update_state(); unlock(); } void t_gui::cb_cancel_failed(int line, const t_response *r) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: failed to cancel call.").arg(line + 1); emit mw_display(s); s = QString().setNum(r->code); s.append(' ').append(r->reason.c_str()); emit mw_display(s); unlock(); } void t_gui::cb_call_answered(t_user *user_config, int line, const t_response *r) { if (line >= NUM_USER_LINES) return; lock(); QString s; setLineFields(line); emit mw_display_header(); s = qApp->translate("GUI", "Line %1: far end answered call.").arg(line + 1); emit mw_display(s); // Put far-end party in line to-field s = ""; s.append(format_sip_address(user_config, r->hdr_to.display, r->hdr_to.uri).c_str()); if (r->hdr_organization.is_populated()) { s.append(", ").append(r->hdr_organization.name.c_str()); } displayTo(s); unlock(); } void t_gui::cb_call_failed(t_user *user_config, int line, const t_response *r) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: call failed.").arg(line + 1); emit mw_display(s); s = QString().setNum(r->code); s.append(' ').append(r->reason.c_str()); emit mw_display(s); // Warnings if (r->hdr_warning.is_populated()) { list l = format_warnings(r->hdr_warning); for (list::iterator i = l.begin(); i != l.end(); i++) { emit mw_display(i->c_str()); } } // Redirect response if (r->get_class() == R_3XX && r->hdr_contact.is_populated()) { list l = r->hdr_contact.contact_list; l.sort(); emit mw_display(qApp->translate("GUI", "The call can be redirected to:")); for (list::iterator i = l.begin(); i != l.end(); i++) { s = format_sip_address(user_config, i->display, i->uri).c_str(); emit mw_display(s); } } // Unsupported extensions if (r->code == R_420_BAD_EXTENSION) { emit mw_display(r->hdr_unsupported.encode().c_str()); } unlock(); } void t_gui::cb_stun_failed_call_ended(int line) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: call failed.").arg(line + 1); emit mw_display(s); unlock(); } void t_gui::cb_call_ended(int line) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: call released.").arg(line + 1); emit mw_display(s); unlock(); } void t_gui::cb_call_established(int line) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: call established.").arg(line + 1); emit mw_display(s); unlock(); } void t_gui::cb_options_response(const t_response *r) { lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Response on terminal capability request: %1 %2") .arg(r->code).arg(r->reason.c_str()); emit mw_display(s); if (r->code == R_408_REQUEST_TIMEOUT) { // The request timed out, so no capabilities are known. unlock(); return; } s = qApp->translate("GUI", "Terminal capabilities of %1").arg(r->hdr_to.uri.encode().c_str()); emit mw_display(s); s = qApp->translate("GUI", "Accepted body types:").append(" "); if (r->hdr_accept.is_populated()) { s.append(r->hdr_accept.get_value().c_str()); } else { s.append(qApp->translate("GUI", "unknown")); } emit mw_display(s); s = qApp->translate("GUI", "Accepted encodings:").append(" "); if (r->hdr_accept_encoding.is_populated()) { s.append(r->hdr_accept_encoding.get_value().c_str()); } else { s.append(qApp->translate("GUI", "unknown")); } emit mw_display(s); s = qApp->translate("GUI", "Accepted languages:").append(" "); if (r->hdr_accept_language.is_populated()) { s.append(r->hdr_accept_language.get_value().c_str()); } else { s.append(qApp->translate("GUI", "unknown")); } emit mw_display(s); s = qApp->translate("GUI", "Allowed requests:").append(" "); if (r->hdr_allow.is_populated()) { s.append(r->hdr_allow.get_value().c_str()); } else { s.append(qApp->translate("GUI", "unknown")); } emit mw_display(s); s = qApp->translate("GUI", "Supported extensions:").append(" "); if (r->hdr_supported.is_populated()) { if (r->hdr_supported.features.empty()) { s.append(qApp->translate("GUI", "none")); } else { s.append(r->hdr_supported.get_value().c_str()); } } else { s.append(qApp->translate("GUI", "unknown")); } emit mw_display(s); s = qApp->translate("GUI", "End point type:").append(" "); if (r->hdr_server.is_populated()) { s.append(r->hdr_server.get_value().c_str()); } else if (r->hdr_user_agent.is_populated()) { // Some end-points put a User-Agent header in the response // instead of a Server header. s.append(r->hdr_user_agent.get_value().c_str()); } else { s.append(qApp->translate("GUI", "unknown")); } emit mw_display(s); unlock(); } void t_gui::cb_reinvite_success(int line, const t_response *r) { // Do not bother GUI user. return; } void t_gui::cb_reinvite_failed(int line, const t_response *r) { // Do not bother GUI user. return; } void t_gui::cb_retrieve_failed(int line, const t_response *r) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: call retrieve failed.").arg(line + 1); emit mw_display(s); s = QString().setNum(r->code); s.append(' ').append(r->reason.c_str()); emit mw_display(s); unlock(); } void t_gui::cb_invalid_reg_resp(t_user *user_config, const t_response *r, const string &reason) { lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "%1, registration failed: %2 %3") .arg(user_config->get_profile_name().c_str()) .arg(r->code) .arg(r->reason.c_str()); emit mw_display(s); emit mw_display(reason.c_str()); emit update_reg_status(); unlock(); } void t_gui::cb_register_success(t_user *user_config, const t_response *r, unsigned long expires, bool first_success) { lock(); QString s; if (first_success) { emit mw_display_header(); s = qApp->translate("GUI", "%1, registration succeeded (expires = %2 seconds)") .arg(user_config->get_profile_name().c_str()) .arg(expires); emit mw_display(s); } emit update_reg_status(); unlock(); } void t_gui::cb_register_failed(t_user *user_config, const t_response *r, bool first_failure) { lock(); QString s; if (first_failure) { emit mw_display_header(); s = qApp->translate("GUI", "%1, registration failed: %2 %3") .arg(user_config->get_profile_name().c_str()) .arg(r->code) .arg(r->reason.c_str()); emit mw_display(s); } emit update_reg_status(); unlock(); } void t_gui::cb_register_stun_failed(t_user *user_config, bool first_failure) { lock(); QString s; if (first_failure) { emit mw_display_header(); s = qApp->translate("GUI", "%1, registration failed: STUN failure") .arg(user_config->get_profile_name().c_str()); emit mw_display(s); } emit update_reg_status(); unlock(); } void t_gui::cb_deregister_success(t_user *user_config, const t_response *r) { lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "%1, de-registration succeeded: %2 %3") .arg(user_config->get_profile_name().c_str()) .arg(r->code) .arg(r->reason.c_str()); emit mw_display(s); emit update_reg_status(); unlock(); } void t_gui::cb_deregister_failed(t_user *user_config, const t_response *r) { lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "%1, de-registration failed: %2 %3") .arg(user_config->get_profile_name().c_str()) .arg(r->code) .arg(r->reason.c_str()); emit mw_display(s); emit update_reg_status(); unlock(); } void t_gui::cb_fetch_reg_failed(t_user *user_config, const t_response *r) { lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "%1, fetching registrations failed: %2 %3") .arg(user_config->get_profile_name().c_str()) .arg(r->code) .arg(r->reason.c_str()); emit mw_display(s); unlock(); } void t_gui::cb_fetch_reg_result(t_user *user_config, const t_response *r) { lock(); QString s; emit mw_display_header(); s = user_config->get_profile_name().c_str(); const list &l = r->hdr_contact.contact_list; if (l.size() == 0) { s += qApp->translate("GUI", ": you are not registered"); emit mw_display(s); } else { s += qApp->translate("GUI", ": you have the following registrations"); emit mw_display(s); for (list::const_iterator i = l.begin(); i != l.end(); i++) { emit mw_display(i->encode().c_str()); } } unlock(); } void t_gui::do_cb_register_inprog(t_user *user_config, t_register_type register_type) { QString s; switch(register_type) { case REG_REGISTER: // Do not report registration refreshments if (phone->get_is_registered(user_config)) break; mainWindow->statRegLabel->setPixmap( QPixmap(":/icons/images/gear.png")); break; case REG_DEREGISTER: case REG_DEREGISTER_ALL: mainWindow->statRegLabel->setPixmap( QPixmap(":/icons/images/gear.png")); break; case REG_QUERY: emit mw_display_header(); s = user_config->get_profile_name().c_str(); s += qApp->translate("GUI", ": fetching registrations..."); emit mw_display(s); break; } } void t_gui::cb_register_inprog(t_user *user_config, t_register_type register_type) { QMetaObject::invokeMethod(this, "do_cb_register_inprog", Qt::QueuedConnection, Q_ARG(t_user*, user_config), Q_ARG(t_register_type, register_type)); } void t_gui::cb_redirecting_request(t_user *user_config, int line, const t_contact_param &contact) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: redirecting request to").arg(line + 1); emit mw_display(s); s = format_sip_address(user_config, contact.display, contact.uri).c_str(); emit mw_display(s); unlock(); } void t_gui::cb_redirecting_request(t_user *user_config, const t_contact_param &contact) { lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Redirecting request to: %1").arg( format_sip_address(user_config, contact.display, contact.uri).c_str()); emit mw_display(s); unlock(); } void t_gui::cb_notify_call(int line, const QString &from_party, const QString &organization, const QImage &photo, const QString &subject, QString &referred_by_party) { if (line >= NUM_USER_LINES) return; lock(); // Play ringtone if the call is received on the active line. if (line == phone->get_active_line() && !phone->is_line_auto_answered(line)) { cb_play_ringtone(line); } // Pop up sys tray balloon #ifdef HAVE_KDE t_twinkle_sys_tray *tray = mainWindow->getSysTray(); if (tray && !sys_tray_popup && !phone->is_line_auto_answered(line)) { QString presFromParty(""); if (!from_party.isEmpty()) { presFromParty = dotted_truncate(from_party.toStdString(), 50).c_str(); } QString presOrganization(""); if (!organization.isEmpty()) { presOrganization = dotted_truncate(organization.toStdString(), 50).c_str(); } QString presSubject(""); if (!subject.isEmpty()) { presSubject = dotted_truncate(subject.toStdString(), 50).c_str(); } QString presReferredByParty(""); if (!referred_by_party.isEmpty()) { presReferredByParty = qApp->translate("GUI", "Transferred by: %1").arg( dotted_truncate(referred_by_party.toStdString(), 40).c_str()); } // Create photo pixmap. If no photo is available, then use // the Twinkle icon. QPixmap pm; Q3Frame::Shape photoFrameShape = Q3Frame::NoFrame; if (photo.isNull()) { pm = QPixmap(":/icons/images/twinkle32.png"); } else { pm.convertFromImage(photo); photoFrameShape = QFrame::Box; } // Create the popup view. sys_tray_popup = new KPassivePopup(tray); MEMMAN_NEW(sys_tray_popup); sys_tray_popup->setAutoDelete(false); sys_tray_popup->setTimeout(0); Q3VBox *popup_view = new Q3VBox(sys_tray_popup); Q3HBox *hb = new Q3HBox(popup_view); hb->setSpacing(5); QLabel *lblPhoto = new QLabel(hb); lblPhoto->setPixmap(pm); lblPhoto->setFrameShape(photoFrameShape); Q3VBox *vb = new Q3VBox(hb); QString captionText("

"); captionText += qApp->translate("SysTrayPopup", "Incoming Call"); captionText += "

"; QLabel *lblCaption = new QLabel(captionText, vb); lblCaption->setAlignment(Qt::AlignTop | Qt::AlignLeft); lblCaption->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); QLabel *lblFrom = new QLabel(presFromParty, vb); lblFrom->setAlignment(Qt::AlignTop | Qt::AlignLeft); lblFrom->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); QLabel *lastLabel = lblFrom; if (!presOrganization.isEmpty()) { QLabel *lblOrganization = new QLabel(presOrganization, vb); lblOrganization->setAlignment(Qt::AlignTop | Qt::AlignLeft); lblOrganization->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); lastLabel = lblOrganization; } if (!presReferredByParty.isEmpty()) { QLabel *lblReferredBy = new QLabel(presReferredByParty, vb); lblReferredBy->setAlignment(Qt::AlignTop | Qt::AlignLeft); lblReferredBy->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); lastLabel = lblReferredBy; } if (!presSubject.isEmpty()) { QLabel *lblSubject = new QLabel(presSubject, vb); lblSubject->setAlignment(Qt::AlignTop | Qt::AlignLeft); lblSubject->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); lastLabel = lblSubject; } lastLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // Answer and reject buttons Q3HBox *buttonBox = new Q3HBox(vb); QIcon iconAnswer(QPixmap(":/icons/images/answer.png")); QPushButton *pbAnswer = new QPushButton(iconAnswer, qApp->translate("SysTrayPopup", "Answer"), buttonBox); QObject::connect(pbAnswer, SIGNAL(clicked()), mainWindow, SLOT(phoneAnswerFromSystrayPopup())); QIcon iconReject(QPixmap(":/icons/images/reject.png")); QPushButton *pbReject = new QPushButton(iconReject, qApp->translate("SysTrayPopup", "Reject"), buttonBox); QObject::connect(pbReject, SIGNAL(clicked()), mainWindow, SLOT(phoneRejectFromSystrayPopup())); sys_tray_popup->setView(popup_view); // Show the popup line_sys_tray_popup = line; sys_tray_popup->show(); QObject::connect(sys_tray_popup, SIGNAL(clicked()), sys_tray_popup, SLOT(hide())); } #endif // Show main window after a few seconds if (sys_config->get_gui_auto_show_incoming()) { autoShowTimer[line].setSingleShot(true); autoShowTimer[line].start( sys_config->get_gui_auto_show_timeout() * 1000); } unlock(); } void t_gui::cb_stop_call_notification(int line) { if (line >= NUM_USER_LINES) return; lock(); cb_stop_tone(line); autoShowTimer[line].stop(); #ifdef HAVE_KDE if (sys_tray_popup && line_sys_tray_popup == line) { sys_tray_popup->hide(); MEMMAN_DELETE(sys_tray_popup); delete sys_tray_popup; sys_tray_popup = NULL; } #endif unlock(); } void t_gui::cb_dtmf_detected(int line, t_dtmf_ev dtmf_event) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: DTMF detected:").arg(line + 1).append(" "); if (is_valid_dtmf_ev(dtmf_event)) { s.append(dtmf_ev2char(dtmf_event)); } else { s.append(qApp->translate("GUI", "invalid DTMF telephone event (%1)").arg( (int)dtmf_event)); } emit mw_display(s); unlock(); } void t_gui::cb_send_dtmf(int line, t_dtmf_ev dtmf_event) { if (line >= NUM_USER_LINES) return; lock(); QString s; if (!is_valid_dtmf_ev(dtmf_event)) return; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: send DTMF %2").arg(line + 1).arg( dtmf_ev2char(dtmf_event)); emit mw_display(s); unlock(); } void t_gui::cb_dtmf_not_supported(int line) { if (line >= NUM_USER_LINES) return; QString s; if (throttle_dtmf_not_supported) return; lock(); emit mw_display_header(); s = qApp->translate("GUI", "Line %1: far end does not support DTMF telephone events.").arg(line + 1); emit mw_display(s); // Throttle subsequent call backs throttle_dtmf_not_supported = true; unlock(); } void t_gui::cb_dtmf_supported(int line) { if (line >= NUM_USER_LINES) return; lock(); emit update_state(); unlock(); } void t_gui::cb_line_state_changed(void) { lock(); emit update_state(); unlock(); } void t_gui::cb_send_codec_changed(int line, t_audio_codec codec) { if (line >= NUM_USER_LINES) return; lock(); displayCodecInfo(line); unlock(); } void t_gui::cb_recv_codec_changed(int line, t_audio_codec codec) { if (line >= NUM_USER_LINES) return; lock(); displayCodecInfo(line); unlock(); } void t_gui::cb_notify_recvd(int line, const t_request *r) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: received notification.").arg(line+1); emit mw_display(s); s = qApp->translate("GUI", "Event: %1").arg(r->hdr_event.event_type.c_str()); emit mw_display(s); s = qApp->translate("GUI", "State: %1").arg(r->hdr_subscription_state.substate.c_str()); emit mw_display(s); if (r->hdr_subscription_state.substate == SUBSTATE_TERMINATED) { s = qApp->translate("GUI", "Reason: %1").arg(r->hdr_subscription_state.reason.c_str()); emit mw_display(s); } t_response *sipfrag = (t_response *)((t_sip_body_sipfrag *)r->body)->sipfrag; s = qApp->translate("GUI", "Progress: %1 %2").arg(sipfrag->code).arg(sipfrag->reason.c_str()); emit mw_display(s); unlock(); } void t_gui::cb_refer_failed(int line, const t_response *r) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: call transfer failed.").arg(line + 1); emit mw_display(s); s = QString().setNum(r->code); s.append(' ').append(r->reason.c_str()); emit mw_display(s); // The refer state has changed, so update the main window. emit update_state(); unlock(); } void t_gui::cb_refer_result_success(int line) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: call successfully transferred.").arg(line + 1); emit mw_display(s); // The refer state has changed, so update the main window. emit update_state(); unlock(); } void t_gui::cb_refer_result_failed(int line) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: call transfer failed.").arg(line + 1); emit mw_display(s); // The refer state has changed, so update the main window. emit update_state(); unlock(); } void t_gui::cb_refer_result_inprog(int line) { if (line >= NUM_USER_LINES) return; lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "Line %1: call transfer still in progress.").arg(line + 1); emit mw_display(s); s = qApp->translate("GUI", "No further notifications will be received."); emit mw_display(s); // The refer state has changed, so update the main window. emit update_state(); unlock(); } void t_gui::cb_call_referred(t_user *user_config, int line, t_request *r) { if (line >= NUM_USER_LINES) return; QString s; lock(); emit mw_display_header(); s = qApp->translate("GUI", "Line %1: transferring call to %2").arg(line +1).arg( format_sip_address(user_config, r->hdr_refer_to.display, r->hdr_refer_to.uri).c_str()); emit mw_display(s); if (r->hdr_referred_by.is_populated()) { s = qApp->translate("GUI", "Transfer requested by %1").arg( format_sip_address(user_config, r->hdr_referred_by.display, r->hdr_referred_by.uri).c_str()); emit mw_display(s); } setLineFields(line); s = format_sip_address(user_config, user_config->get_display(false), user_config->create_user_uri(false)).c_str(); displayFrom(s); photoLabel->hide(); s = format_sip_address(user_config, r->hdr_refer_to.display, r->hdr_refer_to.uri).c_str(); displayTo(s); subjectLabel->clear(); codecLabel->clear(); unlock(); } void t_gui::cb_retrieve_referrer(t_user *user_config, int line) { if (line >= NUM_USER_LINES) return; QString s; lock(); emit mw_display_header(); s = qApp->translate("GUI", "Line %1: Call transfer failed. Retrieving original call.").arg(line + 1); emit mw_display(s); setLineFields(line); const t_call_info call_info = phone->get_call_info(line); s = format_sip_address(user_config, call_info.get_from_display_presentation(), call_info.from_uri).c_str(); if (!call_info.from_organization.empty()) { s += ", "; s += call_info.from_organization.c_str(); } displayFrom(s); // Display photo QImage fromPhoto; if (sys_config->get_ab_lookup_photo()) { t_address_finder *af = t_address_finder::get_instance(); fromPhoto = af->find_photo(user_config, call_info.from_uri); } displayPhoto(fromPhoto); s = format_sip_address(user_config, call_info.to_display, call_info.to_uri).c_str(); if (!call_info.to_organization.empty()) { s += ", "; s += call_info.to_organization.c_str(); } displayTo(s); displaySubject(call_info.subject.c_str()); codecLabel->clear(); unlock(); } void t_gui::cb_consultation_call_setup(t_user *user_config, int line) { if (line >= NUM_USER_LINES) return; QString s; lock(); setLineFields(line); const t_call_info call_info = phone->get_call_info(line); s = format_sip_address(user_config, call_info.get_from_display_presentation(), call_info.from_uri).c_str(); if (!call_info.from_organization.empty()) { s += ", "; s += call_info.from_organization.c_str(); } displayFrom(s); photoLabel->hide(); s = format_sip_address(user_config, call_info.to_display, call_info.to_uri).c_str(); if (!call_info.to_organization.empty()) { s += ", "; s += call_info.to_organization.c_str(); } displayTo(s); displaySubject(call_info.subject.c_str()); codecLabel->clear(); unlock(); } void t_gui::cb_stun_failed(t_user *user_config, int err_code, const string &err_reason) { lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "%1, STUN request failed: %2 %3") .arg(user_config->get_profile_name().c_str()) .arg(err_code).arg(err_reason.c_str()); emit mw_display(s); unlock(); } void t_gui::cb_stun_failed(t_user *user_config) { lock(); QString s; emit mw_display_header(); s = qApp->translate("GUI", "%1, STUN request failed.") .arg(user_config->get_profile_name().c_str()); emit mw_display(s); unlock(); } bool t_gui::cb_ask_user_to_redirect_invite(t_user *user_config, const t_url &destination, const string &display) { bool retval; QMetaObject::invokeMethod(this, "do_cb_ask_user_to_redirect_invite", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, retval), Q_ARG(t_user*, user_config), Q_ARG(const t_url&, destination), Q_ARG(const string&, display)); return retval; } bool t_gui::cb_ask_user_to_redirect_request(t_user *user_config, const t_url &destination, const string &display, t_method method) { bool retval; QMetaObject::invokeMethod(this, "do_cb_ask_user_to_redirect_request", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, retval), Q_ARG(t_user*, user_config), Q_ARG(const t_url&, destination), Q_ARG(const string&, display), Q_ARG(t_method, method)); return retval; } bool t_gui::do_cb_ask_credentials(t_user *user_config, const string &realm, string &username, string &password) { QString user(username.c_str()); QString passwd(password.c_str()); AuthenticationForm *af = new AuthenticationForm(mainWindow); af->setModal(true); MEMMAN_NEW(af); if (!af->exec(user_config, QString(realm.c_str()), user, passwd)) { MEMMAN_DELETE(af); delete af; return false; } username = user.toStdString(); password = passwd.toStdString(); MEMMAN_DELETE(af); delete af; return true; } bool t_gui::cb_ask_credentials(t_user *user_config, const string &realm, string &username, string &password) { bool retval; QMetaObject::invokeMethod(this, "do_cb_ask_credentials", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, retval), Q_ARG(t_user*, user_config), Q_ARG(const string&, realm), Q_ARG(string&, username), Q_ARG(string&, password)); return retval; } bool t_gui::do_cb_ask_user_to_redirect_invite(t_user *user_config, const t_url &destination, const string &display) { QString s; QString title; title = PRODUCT_NAME; title.append(" - ").append(qApp->translate("GUI", "Redirecting call")); s = qApp->translate("GUI", "User profile:").append(" "); s.append(user_config->get_profile_name().c_str()); s.append("
").append(qApp->translate("GUI", "User:")).append(" "); s.append(str2html(user_config->get_display_uri().c_str())); s.append("

"); s.append(qApp->translate("GUI", "Do you allow the call to be redirected to the following destination?")); s.append("

"); s.append(str2html(ui->format_sip_address(user_config, display, destination).c_str())); s.append("

"); s.append(qApp->translate("GUI", "If you don't want to be asked this anymore, then you must change " "the settings in the SIP protocol section of the user profile.")); QMessageBox *mb = new QMessageBox(title, s, QMessageBox::Warning, QMessageBox::Yes, QMessageBox::No, QMessageBox::NoButton, mainWindow); MEMMAN_NEW(mb); bool permission = (mb->exec() == QMessageBox::Yes); MEMMAN_DELETE(mb); delete mb; return permission; } bool t_gui::do_cb_ask_user_to_redirect_request(t_user *user_config, const t_url &destination, const string &display, t_method method) { QString s; QString title; title = PRODUCT_NAME; title.append(" - ").append(qApp->translate("GUI", "Redirecting request")); s = qApp->translate("GUI", "User profile:").append(" "); s.append(user_config->get_profile_name().c_str()); s.append("
").append(qApp->translate("GUI", "User:")).append(" "); s.append(str2html(user_config->get_display_uri().c_str())); s.append("

"); s.append(qApp->translate("GUI", "Do you allow the %1 request to be redirected to the following destination?").arg( method2str(method).c_str())); s.append("

"); s.append(str2html(ui->format_sip_address(user_config, display, destination).c_str())); s.append("

"); s.append(qApp->translate("GUI", "If you don't want to be asked this anymore, then you must change " "the settings in the SIP protocol section of the user profile.")); QMessageBox *mb = new QMessageBox(title, s, QMessageBox::Warning, QMessageBox::Yes, QMessageBox::No, QMessageBox::NoButton, mainWindow); MEMMAN_NEW(mb); bool permission = (mb->exec() == QMessageBox::Yes); MEMMAN_DELETE(mb); delete mb; return permission; } void t_gui::do_cb_ask_user_to_refer(t_user *user_config, const string &refer_to_uri_str, const string &refer_to_display, const string &referred_by_uri_str, const string &referred_by_display) { t_url refer_to_uri(refer_to_uri_str); t_url referred_by_uri(referred_by_uri_str); QString s; QString title; title = PRODUCT_NAME; title.append(" - ").append(qApp->translate("GUI", "Transferring call")); s = qApp->translate("GUI", "User profile:").append(" "); s.append(user_config->get_profile_name().c_str()); s.append("
").append(qApp->translate("GUI", "User:")).append(" "); s.append(str2html(user_config->get_display_uri().c_str())); s.append("

"); if (referred_by_uri.is_valid()) { s.append(qApp->translate("GUI","Request to transfer call received from:")); s.append("
"); s.append(str2html(format_sip_address(user_config, referred_by_display, referred_by_uri).c_str())); s.append("
"); } else { s.append(qApp->translate("GUI", "Request to transfer call received.")); s.append("
"); } s.append("
"); s.append(qApp->translate("GUI", "Do you allow the call to be transferred to the following destination?")); s.append("

"); s.append(str2html(ui->format_sip_address(user_config, refer_to_display, refer_to_uri).c_str())); s.append("

"); s.append(qApp->translate("GUI", "If you don't want to be asked this anymore, then you must change " "the settings in the SIP protocol section of the user profile.")); ReferPermissionDialog *dialog = new ReferPermissionDialog(mainWindow, title, s); // Do not report to MEMMAN as Qt will auto destruct this dialog on close. dialog->show(); } void t_gui::cb_ask_user_to_refer(t_user *user_config, const t_url &refer_to_uri, const string &refer_to_display, const t_url &referred_by_uri, const string &referred_by_display) { QMetaObject::invokeMethod(this, "do_cb_ask_user_to_refer", Q_ARG(t_user*, user_config), Q_ARG(const string&, refer_to_uri.encode()), Q_ARG(const string&, refer_to_display), Q_ARG(const string&, referred_by_uri.encode()), Q_ARG(const string&, referred_by_display)); } void t_gui::cb_show_msg(const string &msg, t_msg_priority prio) { cb_show_msg(NULL, msg, prio); } void t_gui::cb_show_msg(QWidget *parent, const string &msg, t_msg_priority prio) { lock(); switch (prio) { case MSG_INFO: QMessageBox::information(parent, PRODUCT_NAME, msg.c_str()); break; case MSG_WARNING: QMessageBox::warning(parent, PRODUCT_NAME, msg.c_str()); break; case MSG_CRITICAL: default: QMessageBox::critical(parent, PRODUCT_NAME, msg.c_str()); break; } unlock(); } bool t_gui::cb_ask_msg(const string &msg, t_msg_priority prio) { return cb_ask_msg(NULL, msg, prio); } bool t_gui::cb_ask_msg(QWidget *parent, const string &msg, t_msg_priority prio) { lock(); int button = QMessageBox::No; switch (prio) { case MSG_INFO: button = QMessageBox::information(parent, PRODUCT_NAME, msg.c_str(), QMessageBox::Yes, QMessageBox::No | QMessageBox::Escape | QMessageBox::Default); break; case MSG_WARNING: button = QMessageBox::warning(parent, PRODUCT_NAME, msg.c_str(), QMessageBox::Yes, QMessageBox::No | QMessageBox::Escape | QMessageBox::Default); break; case MSG_CRITICAL: default: button = QMessageBox::critical(parent, PRODUCT_NAME, msg.c_str(), QMessageBox::Yes, QMessageBox::No | QMessageBox::Escape | QMessageBox::Default); break; } unlock(); return (button == QMessageBox::Yes); } void t_gui::cb_display_msg(const string &msg, t_msg_priority prio) { // If this thread may not lock the UI, then push the display message on // the UI event queue. The message will be display asynchronously. if (is_prohibited_thread()) { cb_async_display_msg(msg, prio); return; } QString s; lock(); switch (prio) { case MSG_NO_PRIO: break; case MSG_INFO: s = qApp->translate("GUI", "Info:"); break; case MSG_WARNING: s = qApp->translate("GUI", "Warning:"); break; case MSG_CRITICAL: default: s = qApp->translate("GUI", "Critical:"); break; } if (prio == MSG_NO_PRIO) { s = msg.c_str(); } else { s.append(" ").append(msg.c_str()); } emit mw_display_header(); emit mw_display(s); unlock(); } void t_gui::cb_log_updated(bool log_zapped) { emit mw_update_log(log_zapped); } void t_gui::cb_call_history_updated(void) { emit mw_update_call_history(); } void t_gui::cb_missed_call(int num_missed_calls) { emit mw_update_missed_call_status(num_missed_calls); } void t_gui::cb_nat_discovery_progress_start(int num_steps) { natDiscoveryProgressDialog = new QProgressDialog( qApp->translate("GUI", "Firewall / NAT discovery..."), qApp->translate("GUI", "Abort"), 0, num_steps, mainWindow); MEMMAN_NEW(natDiscoveryProgressDialog); natDiscoveryProgressDialog->setWindowTitle(PRODUCT_NAME); natDiscoveryProgressDialog->setMinimumDuration(200); } void t_gui::cb_nat_discovery_progress_step(int step) { natDiscoveryProgressDialog->setValue(step); qApp->processEvents(); } void t_gui::cb_nat_discovery_finished(void) { MEMMAN_DELETE(natDiscoveryProgressDialog); delete natDiscoveryProgressDialog; } bool t_gui::cb_nat_discovery_cancelled(void) { return natDiscoveryProgressDialog->wasCanceled(); } void t_gui::cb_line_encrypted(int line, bool encrypted, const string &cipher_mode) { // Nothing todo in GUI // Encryption state is shown by the line state updata methods on // MphoneForm } void t_gui::cb_show_zrtp_sas(int line, const string &sas) { if (line >= NUM_USER_LINES) return; lock(); QString s; setLineFields(line); emit mw_display_header(); s = qApp->translate("GUI", "Line %1").arg(line + 1); s.append(": SAS = ").append(sas.c_str()); emit mw_display(s); s = qApp->translate("GUI", "Click the padlock to confirm a correct SAS."); emit mw_display(s); unlock(); } void t_gui::cb_zrtp_confirm_go_clear(int line) { t_user *user_config = phone->get_line_user(line); if (!user_config) return; QString msg(qApp->translate("GUI", "The remote user on line %1 disabled the encryption.") .arg(line + 1)); if (user_config->get_zrtp_goclear_warning()) { cb_show_msg(msg.toStdString(), MSG_WARNING); } else { cb_display_msg(msg.toStdString(), MSG_WARNING); } action_zrtp_go_clear_ok(line); } void t_gui::cb_zrtp_sas_confirmed(int line) { lock(); QString s; setLineFields(line); emit mw_display_header(); s = qApp->translate("GUI", "Line %1: SAS confirmed.").arg(line + 1); emit mw_display(s); unlock(); } void t_gui::cb_zrtp_sas_confirmation_reset(int line) { lock(); QString s; setLineFields(line); emit mw_display_header(); s = qApp->translate("GUI", "Line %1: SAS confirmation reset.").arg(line + 1); emit mw_display(s); unlock(); } void t_gui::cb_update_mwi(void) { lock(); emit update_mwi(); unlock(); } void t_gui::cb_mwi_subscribe_failed(t_user *user_config, t_response *r, bool first_failure) { lock(); QString s; if (first_failure) { emit mw_display_header(); s = qApp->translate("GUI", "%1, voice mail status failure.") .arg(user_config->get_profile_name().c_str()); emit mw_display(s); } unlock(); } void t_gui::cb_mwi_terminated(t_user *user_config, const string &reason) { lock(); QString s; if (reason == "EV_REASON_REJECTED") { s = qApp->translate("GUI", "%1, voice mail status rejected."); } else if (reason == "EV_REASON_NORESOURCE") { s = qApp->translate("GUI", "%1, voice mailbox does not exist."); } else { s = qApp->translate("GUI", "%1, voice mail status terminated."); } emit mw_display_header(); emit mw_display(s.arg(user_config->get_profile_name().c_str())); unlock(); } bool t_gui::cb_message_request(t_user *user_config, t_request *r) { string text; im::t_text_format text_format = im::TXT_PLAIN; bool attachment = false; bool failed_to_save_attachment = false; string attachment_error_msg; string attachment_saved_name; string attachment_save_as_name; if (!r->body) { log_file->write_report("Missing body.", "t_gui::cb_message_request", LOG_NORMAL, LOG_DEBUG); return true; } // Determine if message should be shown inline or as an attachment if ((r->hdr_content_disp.is_populated() && r->hdr_content_disp.type == "attachment") || (r->body->get_type() == BODY_OPAQUE) || (r->hdr_content_length.is_populated() && r->hdr_content_length.length > im::MAX_INLINE_TEXT_LEN) || (r->body->encode().size() > im::MAX_INLINE_TEXT_LEN)) { attachment = true; string suggested_file_extension = mime2file_extension(r->hdr_content_type.media); if (!sys_config->save_sip_body(*r, suggested_file_extension, attachment_saved_name, attachment_save_as_name, attachment_error_msg)) { failed_to_save_attachment = true; } } else if (r->body->get_type() == BODY_PLAIN_TEXT) { t_sip_body_plain_text *sb = dynamic_cast(r->body); text = sb->text; text_format = im::TXT_PLAIN; } else if (r->body->get_type() == BODY_HTML_TEXT) { t_sip_body_html_text *sb = dynamic_cast(r->body); text = sb->text; text_format = im::TXT_HTML; } else { log_file->write_header("t_gui::cb_message_request", LOG_NORMAL, LOG_CRITICAL); log_file->write_raw("Unsupported content type: "); log_file->write_raw(r->body->get_type()); log_file->write_endl(); log_file->write_footer(); return true; } if (!text.empty()) { string charset = r->hdr_content_type.media.charset; if (!charset.empty() && cmp_nocase(charset, "utf-8") != 0) { // Try to decode the text QTextCodec *c = QTextCodec::codecForName(charset.c_str()); if (c) { text = c->toUnicode(text.c_str()).toStdString(); } else { log_file->write_header( "t_gui::cb_message_request", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Cannot decode charset: "); log_file->write_raw(charset); log_file->write_endl(); log_file->write_footer(); } } } lock(); // Find an existing session im::t_msg_session *session = getMessageSession(user_config, r->hdr_from.uri, r->hdr_from.get_display_presentation()); if (!session) { // There is no session yet. if (messageSessions.size() >= user_config->get_im_max_sessions()) { log_file->write_report( "Maximum number of message sessions reached. Reject message", "t_gui::cb_message_request"); unlock(); return false; } // Create a new session. session = new im::t_msg_session(user_config, t_display_url(r->hdr_from.uri, r->hdr_from.get_display_presentation())); MEMMAN_NEW(session); addMessageSession(session); MessageFormView *view = new MessageFormView(NULL, session); MEMMAN_NEW(view); view->show(); view->raise(); } // Construct message im::t_msg msg; msg.direction = im::MSG_DIR_IN; if (r->hdr_subject.is_populated()) { msg.subject = r->hdr_subject.subject; } if (!text.empty()) { msg.message = text; msg.format = text_format; } if (attachment && !failed_to_save_attachment) { msg.has_attachment = true; msg.attachment_filename = attachment_saved_name; msg.attachment_save_as_name = attachment_save_as_name; msg.attachment_media = r->hdr_content_type.media; } session->recv_msg(msg); // Set error message if attachment could not be saved if (failed_to_save_attachment) { QString s = qApp->translate("GUI", "Failed to save message attachment: %1").arg(attachment_error_msg.c_str()); session->set_error(s.toStdString()); } unlock(); return true; } void t_gui::cb_message_response(t_user *user_config, t_response *r, t_request *req) { lock(); // Find session associated with the response im::t_msg_session *session = getMessageSession(user_config, r->hdr_to.uri, r->hdr_to.display); if (session) { if (!r->is_success()) { string s = int2str(r->code); s += ' '; s += r->reason; session->set_error(s); } else { if (r->code == R_202_ACCEPTED) { string s; if (r->reason == REASON_202) { s = qApp->translate("GUI", "Accepted by network").toStdString(); } else { s = r->reason; } session->set_delivery_notification(s); } } session->set_msg_in_flight(false); } // If there is no session anymore, then discard the response unlock(); } void t_gui::cb_im_iscomposing_request(t_user *user_config, t_request *r, im::t_composing_state state, time_t refresh) { lock(); // Find an existing session im::t_msg_session *session = getMessageSession(user_config, r->hdr_from.uri, r->hdr_from.get_display_presentation()); if (!session) { // A composing indication does not initiate a new message session. log_file->write_report( "Received composing indication for unknown session.", "t_gui::cb_im_iscomposing_request"); } else { session->set_remote_composing_state(state, refresh); } unlock(); } void t_gui::cb_im_iscomposing_not_supported(t_user *user_config, t_response *r) { lock(); // Find an existing session im::t_msg_session *session = getMessageSession(user_config, r->hdr_to.uri, r->hdr_to.display); if (session) session->set_send_composing_state(false); unlock(); } void t_gui::cmd_call(const string &destination, bool immediate) { QMetaObject::invokeMethod(this, "gui_cmd_call", Q_ARG(const string&, destination), Q_ARG(bool, immediate)); } void t_gui::cmd_quit(void) { lock(); mainWindow->fileExit(); unlock(); } void t_gui::cmd_show(void) { QMetaObject::invokeMethod(this, "gui_cmd_show"); } void t_gui::cmd_hide(void) { QMetaObject::invokeMethod(this, "gui_cmd_hide"); } string t_gui::get_name_from_abook(t_user *user_config, const t_url &u) { string name; lock(); // Search local address book first name = t_userintf::get_name_from_abook(user_config, u); // Search KAddressBook if (name.empty()) { t_address_finder *af = t_address_finder::get_instance(); name = af->find_name(user_config, u); } unlock(); return name; } // User invoked actions on the phone object void t_gui::action_register(list user_list) { run_on_event_queue([=]() { for (list::const_iterator i = user_list.begin(); i != user_list.end(); i++) { phone->pub_registration(*i, REG_REGISTER, DUR_REGISTRATION(*i)); } }); } void t_gui::action_deregister(list user_list, bool dereg_all) { run_on_event_queue([=]() { for (list::const_iterator i = user_list.begin(); i != user_list.end(); i++) { if (dereg_all) { phone->pub_registration(*i, REG_DEREGISTER_ALL); } else { phone->pub_registration(*i, REG_DEREGISTER); } } }); } void t_gui::action_show_registrations(list user_list) { run_on_event_queue([=]() { for (list::const_iterator i = user_list.begin(); i != user_list.end(); i++) { phone->pub_registration(*i, REG_QUERY); } }); } void t_gui::action_invite(t_user *user_config, const t_url &destination, const string &display, const string &subject, bool anonymous) { QString s; // Call can only be made if line is idle int line = phone->get_active_line(); if (phone->get_line_state(line) == LS_BUSY) return; t_url vm_url(expand_destination(user_config, user_config->get_mwi_vm_address())); if (destination != vm_url) { // Store call info for redial last_called_url = destination; last_called_display = display; last_called_subject = subject; last_called_profile = user_config->get_profile_name(); last_called_hide_user = anonymous; } setLineFields(line); // Set party and subject line fields s = ""; s.append(format_sip_address(user_config, display, destination).c_str()); displayTo(s); s = ""; s.append(format_sip_address(user_config, user_config->get_display(false), user_config->create_user_uri(false)).c_str()); displayFrom(s); displaySubject(subject.c_str()); run_on_event_queue([=]() { phone->pub_invite(user_config, destination, display, subject.c_str(), anonymous); }); } void t_gui::action_answer(void) { cb_stop_call_notification(phone->get_active_line()); phone->pub_answer(); } void t_gui::action_bye(void) { phone->pub_end_call(); } void t_gui::action_reject(void) { QString s; cb_stop_call_notification(phone->get_active_line()); phone->pub_reject(); int line = phone->get_active_line(); mainWindow->displayHeader(); s = qApp->translate("GUI", "Line %1: call rejected.").arg(line + 1); mainWindow->display(s); } void t_gui::action_reject(unsigned short line) { QString s; cb_stop_call_notification(line); phone->pub_reject(line); mainWindow->displayHeader(); s = qApp->translate("GUI", "Line %1: call rejected.").arg(line + 1); mainWindow->display(s); } void t_gui::action_redirect(const list &contacts) { QString s; cb_stop_call_notification(phone->get_active_line()); phone->pub_redirect(contacts, 302); int line = phone->get_active_line(); mainWindow->displayHeader(); s = qApp->translate("GUI", "Line %1: call redirected.").arg(line + 1); mainWindow->display(s); } void t_gui::action_refer(const t_url &destination, const string &display) { phone->pub_refer(destination, display); } void t_gui::action_refer(unsigned short line_from, unsigned short line_to) { phone->pub_refer(line_from, line_to); } void t_gui::action_setup_consultation_call(const t_url &destination, const string &display) { phone->pub_setup_consultation_call(destination, display); } void t_gui::action_hold(void) { phone->pub_hold(); } void t_gui::action_retrieve(void) { phone->pub_retrieve(); } void t_gui::action_conference(void) { if (!phone->join_3way(0, 1)) { mainWindow->display(qApp->translate("GUI", "Failed to start conference.")); } } void t_gui::action_mute(bool on) { phone->mute(on); } void t_gui::action_options(void) { phone->pub_options(); } void t_gui::action_options(t_user *user_config, const t_url &contact) { run_on_event_queue([=]() { phone->pub_options(user_config, contact); }); } void t_gui::action_dtmf(const string &digits) { const t_call_info call_info = phone->get_call_info(phone->get_active_line()); throttle_dtmf_not_supported = false; if (!call_info.dtmf_supported) return; for (string::const_iterator i = digits.begin(); i != digits.end(); i++) { if (is_valid_dtmf_sym(*i)) { phone->pub_send_dtmf(*i, call_info.dtmf_inband, call_info.dtmf_info); } } } void t_gui::action_activate_line(unsigned short line) { phone->pub_activate_line(line); } bool t_gui::action_seize(void) { return phone->pub_seize(); } void t_gui::action_unseize(void) { phone->pub_unseize(); } void t_gui::action_confirm_zrtp_sas(int line) { phone->pub_confirm_zrtp_sas(line); } void t_gui::action_confirm_zrtp_sas() { phone->pub_confirm_zrtp_sas(); } void t_gui::action_reset_zrtp_sas_confirmation(int line) { phone->pub_reset_zrtp_sas_confirmation(line); } void t_gui::action_reset_zrtp_sas_confirmation() { phone->pub_reset_zrtp_sas_confirmation(); } void t_gui::action_enable_zrtp(void) { phone->pub_enable_zrtp(); } void t_gui::action_zrtp_request_go_clear(void) { phone->pub_zrtp_request_go_clear(); } void t_gui::action_zrtp_go_clear_ok(unsigned short line) { phone->pub_zrtp_go_clear_ok(line); } void t_gui::srv_dnd(list user_list, bool on) { for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { if (on) { phone->ref_service(*i)->enable_dnd(); } else { phone->ref_service(*i)->disable_dnd(); } } } void t_gui::srv_enable_cf(t_user *user_config, t_cf_type cf_type, const list &cf_dest) { phone->ref_service(user_config)->enable_cf(cf_type, cf_dest); } void t_gui::srv_disable_cf(t_user *user_config, t_cf_type cf_type) { phone->ref_service(user_config)->disable_cf(cf_type); } void t_gui::srv_auto_answer(list user_list, bool on) { for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { phone->ref_service(*i)->enable_auto_answer(on); } } void t_gui::fill_user_combo(QComboBox *cb) { cb->clear(); list user_list = phone->ref_users(); for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { // OLD code: used display uri // cb->insertItem((*i)->get_display_uri().c_str()); cb->addItem((*i)->get_profile_name().c_str()); } cb->setCurrentIndex(0); } QString t_gui::get_last_file_browse_path(void) const { return lastFileBrowsePath; } void t_gui::set_last_file_browse_path(QString path) { lock(); lastFileBrowsePath = path; unlock(); } #ifdef HAVE_KDE unsigned short t_gui::get_line_sys_tray_popup(void) const { return line_sys_tray_popup; } #endif im::t_msg_session *t_gui::getMessageSession(t_user *user_config, const t_url &remote_url, const string &display) const { for (list::const_iterator it = messageSessions.begin(); it != messageSessions.end(); ++it) { if ((*it)->match(user_config, remote_url)) { (*it)->set_display_if_empty(display); return *it; } } return NULL; } void t_gui::addMessageSession(im::t_msg_session *s) { messageSessions.push_back(s); if (!timerUpdateMessageSessions) { timerUpdateMessageSessions = new QTimer(); MEMMAN_NEW(timerUpdateMessageSessions); connect(timerUpdateMessageSessions, SIGNAL(timeout()), this, SLOT(updateTimersMessageSessions())); timerUpdateMessageSessions->start(1000); } } void t_gui::removeMessageSession(im::t_msg_session *s) { messageSessions.remove(s); if (messageSessions.empty()) { timerUpdateMessageSessions->stop(); MEMMAN_DELETE(timerUpdateMessageSessions); delete timerUpdateMessageSessions; timerUpdateMessageSessions = NULL; } } void t_gui::destroyAllMessageSessions(void) { if (timerUpdateMessageSessions) { MEMMAN_DELETE(timerUpdateMessageSessions); delete timerUpdateMessageSessions; timerUpdateMessageSessions = NULL; } for (list::iterator it = messageSessions.begin(); it != messageSessions.end(); ++it) { MEMMAN_DELETE(*it); delete *it; } messageSessions.clear(); } void t_gui::updateTimersMessageSessions() { for (list::iterator it = messageSessions.begin(); it != messageSessions.end(); ++it) { (*it)->dec_local_composing_timeout(); (*it)->dec_remote_composing_timeout(); } } string t_gui::mime2file_extension(t_media media) { string extension; #ifdef HAVE_KDE // If KDE is available then use KDE to retrieve the proper file // extension so we nicely integrate with the desktop settings. string mime = media.type + "/" + media.subtype; KMimeType::Ptr pMime = KMimeType::mimeType(mime.c_str()); const QStringList &patterns = pMime->patterns(); if (!patterns.empty()) { extension = patterns.front().toStdString(); } else { log_file->write_header("t_gui::mime2file_extension", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Cannot find file extension for mime type "); log_file->write_raw(mime); log_file->write_footer(); } #endif return extension; } void t_gui::open_url_in_browser(const QString &url) { string sys_browser = sys_config->get_gui_browser_cmd(); #ifdef HAVE_KDE if (sys_browser.empty()) { KTrader::OfferList offers = KTrader::self()->query("text/html", "Type == 'Application'"); if (!offers.empty()) { KService::Ptr ptr = offers.first(); KURL::List lst; lst.append(url); KRun::run(*ptr, lst); return; } } #endif bool process_started = false; QStringList browsers; if (sys_browser.empty()) { browsers << "xdg-open" << "firefox" << "mozilla" << "netscape" << "opera"; browsers << "galeon" << "epiphany" << "konqueror"; } else { browsers << sys_browser.c_str(); } for (QStringList::Iterator it = browsers.begin(); it != browsers.end(); ++it) { process_started = QProcess::startDetached(*it, QStringList(url)); if (process_started) break; } if (!process_started) { QString msg = qApp->translate("GUI", "Cannot open web browser: %1").arg(url); msg += "\n\n"; msg += qApp->translate("GUI", "Configure your web browser in the system settings."); cb_show_msg(msg.toStdString(), MSG_CRITICAL); } } twinkle-1.10.1/src/gui/gui.h000066400000000000000000000403331277565361200156000ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _GUI_H #define _GUI_H #include "twinkle_config.h" #include "phone.h" #include "userintf.h" #include "im/msg_session.h" #include "messageform.h" #include "qaction.h" #include "qcombobox.h" #include "qlabel.h" #include "qlineedit.h" #include #include "qtimer.h" #include "qtoolbutton.h" #include "qwidget.h" #ifdef HAVE_KDE #include #endif using namespace std; // Forward declaration class MphoneForm; // Length of redial list in combo boxes #define SIZE_REDIAL_LIST 10 // Selection purpose for select user form enum t_select_purpose { SELECT_REGISTER, SELECT_DEREGISTER, SELECT_DEREGISTER_ALL, SELECT_DND, SELECT_AUTO_ANSWER }; QString str2html(const QString &s); void setDisabledIcon(QAction *action, const QString &icon); void setDisabledIcon(QToolButton *toolButton, const QString &icon); class t_gui : public QObject, public t_userintf { Q_OBJECT private: MphoneForm *mainWindow; // List of active instant messaging session. list messageSessions; // Timer to schedule updating of message sessions every second. QTimer *timerUpdateMessageSessions; // Progress dialog for FW/NAT discovery progress bar QProgressDialog *natDiscoveryProgressDialog; // Pointers to line information fields to display information QLineEdit *fromLabel; QLineEdit *toLabel; QLineEdit *subjectLabel; QLabel *codecLabel; QLabel *photoLabel; // Timers to auto show main window on incoming call QTimer autoShowTimer[NUM_USER_LINES]; #ifdef HAVE_KDE // Popup window on system tray for incoming call notification KPassivePopup *sys_tray_popup; unsigned short line_sys_tray_popup; // lineno for popup #endif // Last dir path browsed by the user with a file dialog QString lastFileBrowsePath; // Set the line information field pointers to the fields for 'line' void setLineFields(int line); // Set text inf from, to and subject fields void displayTo(const QString &s); void displayFrom(const QString &s); void displaySubject(const QString &s); // Display the codecs in use for the line void displayCodecInfo(int line); // Display a photo void displayPhoto(const QImage &photo); protected: // The do_* methods perform the commands parsed by the exec_* methods. virtual bool do_invite(const string &destination, const string &display, const string &subject, bool immediate, bool anonymous); virtual void do_redial(void); virtual void do_answer(void); virtual void do_answerbye(void); virtual void do_reject(void); virtual void do_redirect(bool show_status, bool type_present, t_cf_type cf_type, bool action_present, bool enable, int num_redirections, const list &dest_strlist, bool immediate); virtual void do_dnd(bool show_status, bool toggle, bool enable); virtual void do_auto_answer(bool show_status, bool toggle, bool enable); virtual void do_bye(void); virtual void do_hold(void); virtual void do_retrieve(void); virtual bool do_refer(const string &destination, t_transfer_type transfer_type, bool immediate); virtual void do_conference(void); virtual void do_mute(bool show_status, bool toggle, bool enable); virtual void do_dtmf(const string &digits); virtual void do_register(bool reg_all_profiles); virtual void do_deregister(bool dereg_all_profiles, bool dereg_all_devices); virtual void do_fetch_registrations(void); virtual bool do_options(bool dest_set, const string &destination, bool immediate); virtual void do_line(int line); virtual void do_user(const string &profile_name); virtual void do_zrtp(t_zrtp_cmd zrtp_cmd); virtual bool do_message(const string &destination, const string &display, const im::t_msg &msg); virtual void do_presence(t_presence_state::t_basic_state basic_state); virtual void do_quit(void); virtual void do_help(const list &al); private slots: void gui_do_invite(const QString &destination, const QString &display, const QString &subject, bool immediate, bool anonymous); void gui_do_redial(void); void gui_do_answer(void); void gui_do_answerbye(void); void gui_do_reject(void); void gui_do_redirect(bool type_present, t_cf_type cf_type, bool action_present, bool enable, int num_redirections, const std::list &dest_strlist, bool immediate); void gui_do_dnd(bool toggle, bool enable); void gui_do_auto_answer(bool toggle, bool enable); void gui_do_bye(void); void gui_do_hold(void); void gui_do_retrieve(void); void gui_do_refer(const QString &destination, t_transfer_type transfer_type, bool immediate); void gui_do_conference(void); void gui_do_mute(bool toggle, bool enable); void gui_do_dtmf(const QString &digits); void gui_do_user(const QString &profile_name); QString gui_get_current_profile(); void gui_cmd_call(const string &destination, bool immediate); void gui_cmd_show(void); void gui_cmd_hide(void); public: t_gui(t_phone *_phone); virtual ~t_gui(); // Start the GUI void run(void); // Save user interface state to system settings void save_state(void); // Restore user interface state from system settings void restore_state(void); /** Save state to restore a UI session. */ void save_session_state(void); /** Restore UI session state. */ void restore_session_state(void); // Lock the user interface to synchornize output void lock(void); void unlock(void); // Select network interface to use. string select_network_intf(void); // Select a user configuration file. Returns false if selection failed. bool select_user_config(list &config_files); // Clear the contents of the line information fields. After clearing // the field pointers point to the fields for 'line' void clearLineFields(int line); // Call back functions void cb_incoming_call(t_user *user_config, int line, const t_request *r); void cb_call_cancelled(int line); void cb_far_end_hung_up(int line); void cb_answer_timeout(int line); void cb_sdp_answer_not_supported(int line, const string &reason); void cb_sdp_answer_missing(int line); void cb_unsupported_content_type(int line, const t_sip_message *r); void cb_ack_timeout(int line); void cb_100rel_timeout(int line); void cb_prack_failed(int line, const t_response *r); void cb_provisional_resp_invite(int line, const t_response *r); void cb_cancel_failed(int line, const t_response *r); void cb_call_answered(t_user *user_config, int line, const t_response *r); void cb_call_failed(t_user *user_config, int line, const t_response *r); void cb_stun_failed_call_ended(int line); void cb_call_ended(int line); void cb_call_established(int line); void cb_options_response(const t_response *r); void cb_reinvite_success(int line, const t_response *r); void cb_reinvite_failed(int line, const t_response *r); void cb_retrieve_failed(int line, const t_response *r); void cb_invalid_reg_resp(t_user *user_config, const t_response *r, const string &reason); void cb_register_success(t_user *user_config, const t_response *r, unsigned long expires, bool first_success); void cb_register_failed(t_user *user_config, const t_response *r, bool first_failure); void cb_register_stun_failed(t_user *user_config, bool first_failure); void cb_deregister_success(t_user *user_config, const t_response *r); void cb_deregister_failed(t_user *user_config, const t_response *r); void cb_fetch_reg_failed(t_user *user_config, const t_response *r); void cb_fetch_reg_result(t_user *user_config, const t_response *r); void cb_register_inprog(t_user *user_config, t_register_type register_type); void cb_redirecting_request(t_user *user_config, int line, const t_contact_param &contact); void cb_redirecting_request(t_user *user_config, const t_contact_param &contact); void cb_notify_call(int line, const QString &from_party, const QString &organization, const QImage &photo, const QString &subject, QString &referred_by_party); void cb_stop_call_notification(int line); void cb_dtmf_detected(int line, t_dtmf_ev dtmf_event); void cb_send_dtmf(int line, t_dtmf_ev dtmf_event); void cb_dtmf_not_supported(int line); void cb_dtmf_supported(int line); void cb_line_state_changed(void); void cb_send_codec_changed(int line, t_audio_codec codec); void cb_recv_codec_changed(int line, t_audio_codec codec); void cb_notify_recvd(int line, const t_request *r); void cb_refer_failed(int line, const t_response *r); void cb_refer_result_success(int line); void cb_refer_result_failed(int line); void cb_refer_result_inprog(int line); // A call is being referred by the far end. r must be the REFER request. void cb_call_referred(t_user *user_config, int line, t_request *r); // The reference failed. Call to referrer is retrieved. void cb_retrieve_referrer(t_user *user_config, int line); // A consulation call for a call transfer is being setup. void cb_consultation_call_setup(t_user *user_config, int line); // STUN errors void cb_stun_failed(t_user *user_config, int err_code, const string &err_reason); void cb_stun_failed(t_user *user_config); // Interactive call back functions bool cb_ask_user_to_redirect_invite(t_user *user_config, const t_url &destination, const string &display); bool cb_ask_user_to_redirect_request(t_user *user_config, const t_url &destination, const string &display, t_method method); bool cb_ask_credentials(t_user *user_config, const string &realm, string &username, string &password); // Ask questions asynchronously. void cb_ask_user_to_refer(t_user *user_config, const t_url &refer_to_uri, const string &refer_to_display, const t_url &referred_by_uri, const string &referred_by_display); // Show an error message to the user. Depending on the interface mode // the user has to acknowledge the error before processing continues. void cb_show_msg(const string &msg, t_msg_priority prio = MSG_INFO); void cb_show_msg(QWidget *parent, const string &msg, t_msg_priority prio = MSG_INFO); // Ask a yes/no question to the user. // Returns true for yes and false for no. bool cb_ask_msg(const string &msg, t_msg_priority prio = MSG_INFO); bool cb_ask_msg(QWidget *parent, const string &msg, t_msg_priority prio = MSG_INFO); // Display an error message. void cb_display_msg(const string &msg, t_msg_priority prio = MSG_INFO); // Log file has been updated void cb_log_updated(bool log_zapped = false); // Call history has been updated void cb_call_history_updated(void); void cb_missed_call(int num_missed_calls); // Show firewall/NAT discovery progress void cb_nat_discovery_progress_start(int num_steps); void cb_nat_discovery_progress_step(int step); void cb_nat_discovery_finished(void); bool cb_nat_discovery_cancelled(void); // ZRTP void cb_line_encrypted(int line, bool encrypted, const string &cipher_mode = ""); void cb_show_zrtp_sas(int line, const string &sas); void cb_zrtp_confirm_go_clear(int line); void cb_zrtp_sas_confirmed(int line); void cb_zrtp_sas_confirmation_reset(int line); // MWI void cb_update_mwi(void); void cb_mwi_subscribe_failed(t_user *user_config, t_response *r, bool first_failure); void cb_mwi_terminated(t_user *user_config, const string &reason); // Instant messaging bool cb_message_request(t_user *user_config, t_request *r); void cb_message_response(t_user *user_config, t_response *r, t_request *req); void cb_im_iscomposing_request(t_user *user_config, t_request *r, im::t_composing_state state, time_t refresh); void cb_im_iscomposing_not_supported(t_user *user_config, t_response *r); // Execute external commands void cmd_call(const string &destination, bool immediate); void cmd_quit(void); void cmd_show(void); void cmd_hide(void); // Lookup a URL in the address book string get_name_from_abook(t_user *user_config, const t_url &u); // Actions void action_register(list user_list); void action_deregister(list user_list, bool dereg_all); void action_show_registrations(list user_list); void action_invite(t_user *user_config, const t_url &destination, const string &display, const string &subject, bool anonymous); void action_answer(void); void action_bye(void); void action_reject(void); void action_reject(unsigned short line); void action_redirect(const list &contacts); void action_refer(const t_url &destination, const string &display); void action_refer(unsigned short line_from, unsigned short line_to); void action_setup_consultation_call(const t_url &destination, const string &display); void action_hold(void); void action_retrieve(void); void action_conference(void); void action_mute(bool on); void action_options(void); void action_options(t_user *user_config, const t_url &contact); void action_dtmf(const string &digits); void action_activate_line(unsigned short line); bool action_seize(void); void action_unseize(void); void action_confirm_zrtp_sas(int line); void action_confirm_zrtp_sas(); void action_reset_zrtp_sas_confirmation(int line); void action_reset_zrtp_sas_confirmation(); void action_enable_zrtp(void); void action_zrtp_request_go_clear(void); void action_zrtp_go_clear_ok(unsigned short line); // Service (de)activation void srv_dnd(list user_list, bool on); void srv_enable_cf(t_user *user_config, t_cf_type cf_type, const list &cf_dest); void srv_disable_cf(t_user *user_config, t_cf_type cf_type); void srv_auto_answer(list user_list, bool on); // Fill a combo box with user names (display, uri) of active users void fill_user_combo(QComboBox *cb); // Get/set last dir path for a file dialog browse session QString get_last_file_browse_path(void) const; void set_last_file_browse_path(QString path); #ifdef HAVE_KDE // Get the line associated with the sys tray popup unsigned short get_line_sys_tray_popup(void) const; #endif // Get the message session for a dialog between the user // and the remote url. If the display name was not known // to the session yet, it is set to the passed display. // Returns NULL if no form exists. im::t_msg_session *getMessageSession(t_user *user_config, const t_url &remote_url, const string &display) const; void addMessageSession(im::t_msg_session *s); void removeMessageSession(im::t_msg_session *s); void destroyAllMessageSessions(void); /** * Convert a mime type to a file extension. * @param media [in] The mime type. * @return file extension as glob expression. */ string mime2file_extension(t_media media); /** * Open a URL in an external web browser. * @param url [in] URL to open. */ void open_url_in_browser(const QString &url); signals: void update_reg_status(); void update_mwi(); void update_state(); void mw_display(const QString& s); void mw_display_header(); void mw_update_log(bool log_zapped); void mw_update_call_history(); void mw_update_missed_call_status(int num_missed_calls); private slots: /** * Update timers associated with message sessions. This * function should be called every second. */ void updateTimersMessageSessions(); bool do_cb_ask_user_to_redirect_invite(t_user *user_config, const t_url &destination, const string &display); bool do_cb_ask_user_to_redirect_request(t_user *user_config, const t_url &destination, const string &display, t_method method); bool do_cb_ask_credentials(t_user *user_config, const string &realm, string &username, string &password); void do_cb_ask_user_to_refer(t_user *user_config, const string &refer_to_uri_str, const string &refer_to_display, const string &referred_by_uri_str, const string &referred_by_display); void do_cb_register_inprog(t_user *user_config, t_register_type register_type); }; #endif twinkle-1.10.1/src/gui/historyform.cpp000066400000000000000000000324731277565361200177420ustar00rootroot00000000000000//Added by qt3to4: #include #include #include /* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "call_history.h" #include "util.h" #include "gui.h" #include "qicon.h" #include "audits/memman.h" #include "historyform.h" #include #define HISTCOL_TIMESTAMP 0 #define HISTCOL_DIRECTION 1 #define HISTCOL_FROMTO 2 #define HISTCOL_SUBJECT 3 #define HISTCOL_STATUS 4 /* * Constructs a HistoryForm as a child of 'parent', with the * name 'name' and widget flags set to 'f'. * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ HistoryForm::HistoryForm(QWidget* parent) : QDialog(parent) { setupUi(this); init(); } /* * Destroys the object and frees any allocated resources */ HistoryForm::~HistoryForm() { destroy(); // no need to delete child widgets, Qt does it all for us } /* * Sets the strings of the subwidgets using the current * language. */ void HistoryForm::languageChange() { retranslateUi(this); } void HistoryForm::init() { m_model = new QStandardItemModel(historyListView); historyListView->setModel(m_model); m_model->setColumnCount(5); m_model->setHorizontalHeaderLabels(QStringList() << tr("Time") << tr("In/Out") << tr("From/To") << tr("Subject") << tr("Status")); historyListView->horizontalHeader()->setSortIndicator(HISTCOL_TIMESTAMP, Qt::DescendingOrder); #if QT_VERSION >= 0x050000 historyListView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); #else historyListView->horizontalHeader()->setResizeMode(QHeaderView::ResizeToContents); #endif connect(historyListView->selectionModel(), SIGNAL(currentChanged(QModelIndex, QModelIndex)), SLOT(showCallDetails(QModelIndex))); inCheckBox->setChecked(true); outCheckBox->setChecked(true); successCheckBox->setChecked(true); missedCheckBox->setChecked(true); profileCheckBox->setChecked(true); timeLastViewed = phone->get_startup_time(); QIcon inviteIcon(QPixmap(":/icons/images/invite.png")); QIcon deleteIcon(QPixmap(":/icons/images/editdelete.png")); histPopupMenu = new QMenu(this); itemCall = histPopupMenu->addAction(inviteIcon, tr("Call..."), this, SLOT(call())); histPopupMenu->addAction(deleteIcon, tr("Delete"), this, SLOT(deleteEntry())); m_pixmapIn = QPixmap(":/icons/images/1leftarrow-yellow.png"); m_pixmapOut = QPixmap(":/icons/images/1rightarrow.png"); m_pixmapOk = QPixmap(":/icons/images/ok.png"); m_pixmapCancel = QPixmap(":/icons/images/cancel.png"); } void HistoryForm::destroy() { } void HistoryForm::loadHistory() { // Create list of all active profile names QStringList profile_name_list; listuser_list = phone->ref_users(); for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { profile_name_list.append((*i)->get_profile_name().c_str()); } // Fill the history table unsigned long numberOfCalls = 0; unsigned long totalCallDuration = 0; unsigned long totalConversationDuration = 0; m_model->setRowCount(0); std::list history; call_history->get_history(history); m_history = QList::fromStdList(history); for (int x = 0; x < m_history.size(); x++) { const t_call_record* cr = &m_history[x]; if (cr->direction == t_call_record::DIR_IN && !inCheckBox->isChecked()) { continue; } if (cr->direction == t_call_record::DIR_OUT && !outCheckBox->isChecked()) { continue; } if (cr->invite_resp_code < 300 && !successCheckBox->isChecked()) { continue; } if (cr->invite_resp_code >= 300 && !missedCheckBox->isChecked()) { continue; } if (!profile_name_list.contains(cr->user_profile.c_str()) && profileCheckBox->isChecked()) { continue; } numberOfCalls++; // Calculate total duration totalCallDuration += cr->time_end - cr->time_start; if (cr->time_answer != 0) { totalConversationDuration += cr->time_end - cr->time_answer; } t_user *user_config = phone->ref_user_profile(cr->user_profile); // If the user profile is not active, then use the // first user profile for formatting if (!user_config) { user_config = phone->ref_users().front(); } m_model->setRowCount(numberOfCalls); for (int j = 0; j < 5; j++) { QModelIndex index = m_model->index(m_model->rowCount()-1, j); m_model->setData(index, QVariant(x), Qt::UserRole); switch (j) { case HISTCOL_TIMESTAMP: { m_model->setData(index, QDateTime::fromTime_t(cr->time_start)); break; } case HISTCOL_DIRECTION: { m_model->setData(index, QString::fromStdString(cr->get_direction())); m_model->setData(index, (cr->direction == t_call_record::DIR_IN ? m_pixmapIn : m_pixmapOut), Qt::DecorationRole); break; } case HISTCOL_FROMTO: { std::string address; address = (cr->direction == t_call_record::DIR_IN ? ui->format_sip_address(user_config, cr->from_display, cr->from_uri) : ui->format_sip_address(user_config, cr->to_display, cr->to_uri)); m_model->setData(index, QString::fromStdString(address)); m_model->setData(index, (cr->invite_resp_code < 300 ? m_pixmapOk : m_pixmapCancel), Qt::DecorationRole); break; } case HISTCOL_SUBJECT: { m_model->setData(index, QString::fromStdString(cr->subject)); break; } case HISTCOL_STATUS: { m_model->setData(index, QString::fromStdString(cr->invite_resp_reason)); break; } } } } numberCallsValueTextLabel->setText(QString().setNum(numberOfCalls)); // Total call duration formatting QString durationText = duration2str(totalCallDuration).c_str(); durationText += " ("; durationText += tr("conversation"); durationText += ": "; durationText += duration2str(totalConversationDuration).c_str(); durationText += ")"; totalDurationValueTextLabel->setText(durationText); // Sort entries using currently selected sort column and order. historyListView->sortByColumn(historyListView->horizontalHeader()->sortIndicatorSection(), historyListView->horizontalHeader()->sortIndicatorOrder()); // Make the first entry the selected entry. if (numberOfCalls) historyListView->selectRow(0); } // Update history when triggered by a call back function on the user // interface. void HistoryForm::update() { // There is no need to update the history when the window is // hidden. if (isVisible()) loadHistory(); } void HistoryForm::show() { if (isVisible()) { raise(); activateWindow(); return; } loadHistory(); QDialog::show(); raise(); } void HistoryForm::closeEvent( QCloseEvent *e ) { struct timeval t; gettimeofday(&t, NULL); timeLastViewed = t.tv_sec; // If Twinkle is terminated while the history window is // shown, then the call_history object is destroyed, before this // window is closed. if (call_history) { call_history->clear_num_missed_calls(); } QDialog::closeEvent(e); } void HistoryForm::showCallDetails(const QModelIndex &index) { cdrTextEdit->clear(); if (!index.isValid()) return; int x = m_model->data(index, Qt::UserRole).toInt(); const t_call_record& cr = m_history[x]; t_user *user_config = phone->ref_user_profile(cr.user_profile); // If the user profile is not active, then use the // first user profile for formatting if (!user_config) { user_config = phone->ref_users().front(); } QString s = ""; // Left column: header names s += ""; // Right column: values s += ""; s += "
"; s += tr("Call start:") + "
"; s += tr("Call answer:") + "
"; s += tr("Call end:") + "
"; s += tr("Call duration:") + "
"; s += tr("Direction:") + "
"; s += tr("From:") + "
"; s += tr("To:") + "
"; if (cr.reply_to_uri.is_valid()) s += tr("Reply to:") + "
"; if (cr.referred_by_uri.is_valid()) s += tr("Referred by:") + "
"; s += tr("Subject:") + "
"; s += tr("Released by:") + "
"; s += tr("Status:") + "
"; if (!cr.far_end_device.empty()) s += tr("Far end device:") + "
"; s += tr("User profile:"); s += "
"; s += time2str(cr.time_start, "%d %b %Y %H:%M:%S").c_str(); s += "
"; if (cr.time_answer != 0) { s += time2str(cr.time_answer, "%d %b %Y %H:%M:%S").c_str(); } s += "
"; s += time2str(cr.time_end, "%d %b %Y %H:%M:%S").c_str(); s += "
"; s += duration2str((unsigned long)(cr.time_end - cr.time_start)).c_str(); if (cr.time_answer != 0) { s += " ("; s += tr("conversation"); s += ": "; s += duration2str((unsigned long)(cr.time_end - cr.time_answer)).c_str(); s += ")"; } s += "
"; s += cr.get_direction().c_str(); s += "
"; s += str2html(ui->format_sip_address(user_config, cr.from_display, cr.from_uri).c_str()); if (cr.from_organization != "") { s += ", "; s += str2html(cr.from_organization.c_str()); } s += "
"; s += str2html(ui->format_sip_address(user_config, cr.to_display, cr.to_uri).c_str()); if (cr.to_organization != "") { s += ", "; s += str2html(cr.to_organization.c_str()); } s += "
"; if (cr.reply_to_uri.is_valid()) { s += str2html(ui->format_sip_address(user_config, cr.reply_to_display, cr.reply_to_uri).c_str()); s += "
"; } if (cr.referred_by_uri.is_valid()) { s += str2html(ui->format_sip_address(user_config, cr.referred_by_display, cr.referred_by_uri).c_str()); s += "
"; } s += str2html(cr.subject.c_str()); s += "
"; s += cr.get_rel_cause().c_str(); s += "
"; s += int2str(cr.invite_resp_code).c_str(); s += ' '; s += str2html(cr.invite_resp_reason.c_str()); s += "
"; if (!cr.far_end_device.empty()) { s += str2html(cr.far_end_device.c_str()); s += "
"; } s += str2html(cr.user_profile.c_str()); s += "
"; cdrTextEdit->setText(s); } void HistoryForm::popupMenu(QPoint pos) { if (!historyListView->selectionModel()->hasSelection()) return; QModelIndex index = historyListView->selectionModel()->currentIndex(); int x = m_model->data(index, Qt::UserRole).toInt(); const t_call_record& cr = m_history[x]; // An anonymous caller cannot be called bool canCall = !(cr.direction == t_call_record::DIR_IN && cr.from_uri.encode() == ANONYMOUS_URI); itemCall->setEnabled(canCall); histPopupMenu->popup(pos); } void HistoryForm::call(QModelIndex index) { int i = m_model->data(index, Qt::UserRole).toInt(); const t_call_record& cr = m_history[i]; t_user *user_config = phone->ref_user_profile(cr.user_profile); // If the user profile is not active, then use the first profile if (!user_config) { user_config = phone->ref_users().front(); } // Determine subject QString subject; if (cr.direction == t_call_record::DIR_IN) { if (!cr.subject.empty()) { if (cr.subject.substr(0, tr("Re:").length()) != tr("Re:").toStdString()) { subject = tr("Re:").append(" "); subject += cr.subject.c_str(); } else { subject = cr.subject.c_str(); } } } else { subject = cr.subject.c_str(); } // Send call signal if (cr.direction == t_call_record::DIR_IN && cr.reply_to_uri.is_valid()) { // Call to the Reply-To contact emit call(user_config, ui->format_sip_address(user_config, cr.reply_to_display, cr.reply_to_uri).c_str(), subject, false); } else { // For incoming calls, call to the From contact // For outgoing calls, call to the To contact bool hide_user = false; if (cr.direction == t_call_record::DIR_OUT && cr.from_uri.encode() == ANONYMOUS_URI) { hide_user = true; } emit call(user_config, m_model->data(m_model->index(index.row(), HISTCOL_FROMTO)).toString(), subject, hide_user); } } void HistoryForm::call(void) { QModelIndex index = historyListView->selectionModel()->currentIndex(); if (index.isValid()) call(index); } void HistoryForm::deleteEntry(void) { QModelIndex index = historyListView->selectionModel()->currentIndex(); int i = m_model->data(index, Qt::UserRole).toInt(); m_model->removeRow(index.row()); call_history->delete_call_record(m_history[i].get_id()); } void HistoryForm::clearHistory() { call_history->clear(); m_model->setRowCount(0); } twinkle-1.10.1/src/gui/historyform.h000066400000000000000000000020701277565361200173750ustar00rootroot00000000000000#ifndef HISTORYFORM_H #define HISTORYFORM_H #include "phone.h" #include #include #include #include "user.h" #include "ui_historyform.h" class HistoryForm : public QDialog, public Ui::HistoryForm { Q_OBJECT public: HistoryForm(QWidget* parent = 0); ~HistoryForm(); public slots: virtual void loadHistory(); virtual void update(); virtual void show(); virtual void closeEvent( QCloseEvent * e ); virtual void popupMenu( QPoint pos ); virtual void call( QModelIndex index ); virtual void call( void ); virtual void deleteEntry( void ); virtual void clearHistory(); signals: void call(t_user *, const QString &, const QString &, bool); protected slots: virtual void languageChange(); private: time_t timeLastViewed; QMenu *histPopupMenu; QStandardItemModel *m_model; QAction* itemCall; QPixmap m_pixmapIn, m_pixmapOut; QPixmap m_pixmapOk, m_pixmapCancel; QList m_history; void init(); void destroy(); private slots: void showCallDetails(const QModelIndex &); }; #endif twinkle-1.10.1/src/gui/historyform.ui000066400000000000000000000273011277565361200175670ustar00rootroot00000000000000 HistoryForm 0 0 864 639 Qt::CustomContextMenu Twinkle - Call History QAbstractItemView::SingleSelection QAbstractItemView::SelectRows QAbstractItemView::NoEditTriggers true false false Call details Details of the selected call record. QTextEdit::AutoAll true View Check this option to show incoming calls. &Incoming calls Alt+I Check this option to show outgoing calls. &Outgoing calls Alt+O Check this option to show answered calls. &Answered calls Alt+A Check this option to show missed calls. &Missed calls Alt+M Check this option to show only calls associated with this user profile. Current &user profiles only Alt+U <p>Clear the complete call history.</p> <p><b>Note:</b> this will clear <b>all</b> records, also records not shown depending on the checked view options.</p> C&lear Alt+L Qt::Horizontal QSizePolicy::Expanding 540 20 Close this window. Clo&se Alt+S false Call selected address. &Call Alt+C true Number of calls: false ### false Total call duration: false ### false Qt::Horizontal QSizePolicy::Expanding 460 20 historyListView cdrTextEdit inCheckBox outCheckBox successCheckBox missedCheckBox profileCheckBox clearPushButton closePushButton user.h phone.h closePushButton clicked() HistoryForm close() 713 631 20 20 inCheckBox toggled(bool) HistoryForm loadHistory() 689 356 20 20 missedCheckBox toggled(bool) HistoryForm loadHistory() 689 494 20 20 outCheckBox toggled(bool) HistoryForm loadHistory() 689 402 20 20 profileCheckBox toggled(bool) HistoryForm loadHistory() 689 540 20 20 successCheckBox toggled(bool) HistoryForm loadHistory() 689 448 20 20 clearPushButton clicked() HistoryForm clearHistory() 27 631 20 20 callPushButton clicked() HistoryForm call() 799 631 20 20 historyListView doubleClicked(QModelIndex) HistoryForm call(QModelIndex) 827 91 862 300 historyListView customContextMenuRequested(QPoint) HistoryForm popupMenu(QPoint) 819 45 858 290 call(QModelIndex) popupMenu(QPoint) twinkle-1.10.1/src/gui/icons.qrc000066400000000000000000000161531277565361200164700ustar00rootroot00000000000000 images/answer-disabled.png images/answer.png images/attach.png images/auto_answer-disabled.png images/auto_answer.png images/buddy.png images/bye-disabled.png images/bye.png images/cancel-disabled.png images/cancel.png images/cf-disabled.png images/cf.png images/clock.png images/conf-disabled.png images/conference-disabled.png images/conference.png images/conf.png images/consult-xfer.png images/contexthelp.png images/dtmf-a.png images/dtmf-b.png images/dtmf-c.png images/dtmf-disabled.png images/dtmf-d.png images/dtmf.png images/dtmf-pound.png images/dtmf-star.png images/dtmf-0.png images/dtmf-1.png images/dtmf-2.png images/dtmf-3.png images/dtmf-4.png images/dtmf-5.png images/dtmf-6.png images/dtmf-7.png images/dtmf-8.png images/dtmf-9.png images/editcopy images/editcut images/editdelete.png images/editpaste images/edit.png images/edit16.png images/encrypted-disabled.png images/encrypted.png images/encrypted_verified.png images/encrypted32.png images/exit.png images/favorites.png images/filenew images/fileopen-disabled.png images/fileopen.png images/filesave images/gear.png images/hold-disabled.png images/hold.png images/invite-disabled.png images/invite.png images/kcmpci.png images/kcmpci16.png images/kmix.png images/knotify.png images/kontact_contacts-disabled.png images/kontact_contacts.png images/kontact_contacts32.png images/log.png images/log_small.png images/message.png images/message32.png images/mime_application.png images/mime_audio.png images/mime_image.png images/mime_text.png images/mime_video.png images/missed-disabled.png images/missed.png images/mute-disabled.png images/mute.png images/mwi_failure16.png images/mwi_new16.png images/mwi_none.png images/mwi_none16_dis.png images/mwi_none16.png images/network.png images/no-indication.png images/ok.png images/package_network.png images/package_system.png images/password.png images/penguin_big.png images/penguin.png images/penguin-small.png images/presence_failed.png images/presence_offline.png images/presence_online.png images/presence.png images/presence_rejected.png images/presence_unknown.png images/print images/qt-logo.png images/redial-disabled.png images/redial.png images/redirect-disabled.png images/redirect.png images/redo images/reg_failed-disabled.png images/reg_failed.png images/reg-query.png images/reject-disabled.png images/reject.png images/save_as.png images/searchfind images/settings.png images/stat_conference.png images/stat_established_nomedia.png images/stat_established.png images/stat_mute.png images/stat_outgoing.png images/stat_ringing.png images/sys_auto_ans_dis.png images/sys_auto_ans.png images/sys_busy_estab_dis.png images/sys_busy_estab.png images/sys_busy_trans_dis.png images/sys_busy_trans.png images/sys_dnd_dis.png images/sys_dnd.png images/sys_encrypted_dis.png images/sys_encrypted.png images/sys_encrypted_verified_dis.png images/sys_encrypted_verified.png images/sys_hold_dis.png images/sys_hold.png images/sys_idle_dis.png images/sys_idle.png images/sys_missed_dis.png images/sys_missed.png images/sys_mute_dis.png images/sys_mute.png images/sys_mwi_dis.png images/sys_mwi.png images/sys_redir_dis.png images/sys_redir.png images/sys_services_dis.png images/sys_services.png images/telephone-hook.png images/transfer-disabled.png images/transfer.png images/twinkle16-disabled.png images/twinkle16.png images/twinkle24.png images/twinkle32.png images/twinkle48.png images/undo images/yast_babelfish.png images/yast_PhoneTTOffhook.png images/1downarrow.png images/1leftarrow.png images/1leftarrow-yellow.png images/1rightarrow.png images/1uparrow.png images/osd_hangup.png images/osd_mic_off.png images/osd_mic_on.png images/popup_incoming_answer.png images/popup_incoming_reject.png twinkle-1.10.1/src/gui/images/000077500000000000000000000000001277565361200161055ustar00rootroot00000000000000twinkle-1.10.1/src/gui/images/1downarrow.png000066400000000000000000000011031277565361200207110ustar00rootroot00000000000000PNG  IHDRa IDAT8œOTQsee]  굲0VtFI 6&B  f6qDU=I&s2df?fK SWjyG)mhO{~].[Q^]NYYL'6P}/B4~b*U iq ];]`P@҈k`CYWT܂=3r"Z=E_8Sʐ/C+ ^~qqd(O$}[E*(il>GO0"3[ =:w:6/ ?V+B)B[S&_ (]9Rp]\ 4F.۽ $0,Ql)p3HhD[&wnU`Yxĭhۦ5\lBSAjCORzKTp'J&B<)=RM#4{R*ť?;d"4J^.}]]Z[O4|nfZPJ%&0#.ce`Yvu4K+ϋQ9]ԑp}37mu#~r[?+;,N'[*'IENDB`twinkle-1.10.1/src/gui/images/1leftarrow.png000066400000000000000000000011461277565361200207030ustar00rootroot00000000000000PNG  IHDRa-IDAT8OHTQoޛoFԱYJXP@%"AݴlUԮ@!"#BjE )ZbF={btq{s}߽WRnѢa4=G¨zHQyޡ5ـG?OvO}wL[w29~zDV^)e2i@;{8Z _X. emgZ^15ӰlBP@A:Nۆ<<8te"Z9e~~0B\ ͚6H V'R#gpmX_@㔑_{u8ؔiͬ0uv9=fknVKp%x5lX+kMDjrr,. +ѡ4!ȥ/{lܒ!*^cf;3S~#|el̾^& A]tG'P}sϳ3KAmoo2 c<0?gX+RY^ 'b eM#?*61pIENDB`twinkle-1.10.1/src/gui/images/1rightarrow.png000066400000000000000000000011661277565361200210700ustar00rootroot00000000000000PNG  IHDRa=IDAT8MHTQ}3&S+cД>HE %QMm H"*E> HlN(* C̥",#? qof{w[uۖ1e0_/&' L{dgG/ws(--s31Wo?t̋bJPDMGx "98);ohnq\k _9Zp`UqCa$-u,Vub tqaIENDB`twinkle-1.10.1/src/gui/images/1uparrow.png000066400000000000000000000011121277565361200203660ustar00rootroot00000000000000PNG  IHDRaIDAT8œKTaw'lr rʍMYD]JjղU.\k7 Zh P+;-$"<y89k- gWl@^$0hnDׯ?QBCbz;0JOD^<|s֨%S62.&53x?],ΧZloC;sΝw5ǧuXS;95:ՈØ^! @XS9^*PIpFJEYp;K]:A>#X݀TK_ry [c= & }'V[ hi2##W+»M=0z,P$4os6cX ~Wlg;FoǃWtZG[Hcc #&VeRa7L?'lIENDB`twinkle-1.10.1/src/gui/images/answer-disabled.png000066400000000000000000000005621277565361200216620ustar00rootroot00000000000000PNG  IHDRFbKGD̿ pHYs  d_tIME3lةIDATx-OAEn&kq$5"* jjE/j 5UD !H@\Lfxܗ9sߛGDSQy 0-2$a#9MS2RZH")gr:lϹcڀ5}yi@9dPsY]ˑ l.51~tx ;tZ YYԓ B,W\sČwG'uˆW~O9̀Sϐ?Q!U=k[IENDB`twinkle-1.10.1/src/gui/images/answer.png000066400000000000000000000005751277565361200201210ustar00rootroot00000000000000PNG  IHDRVΎWDIDATx1N@ E_"Qp :< +P-\¾G2L1Mv,elǞ."ۭ%.~AK.Ż%2W#Y$"4\(͒; t8Yi4SA ̪^ݒ2ߝ'``&Lт*vP P᪫J(DF^{*3}?=O';m푍sTJ 6ɹJlD8 fJY|"ǹv (""BA,֋-I{M6Ï."BuÑ/BKD wpP mOrP&Q* q)< ('D,F0ż>3Z'ّE 69 "DZЧEMsV *cL/HR 9Df[Xpw#@Ƞu xbxV#x@c|NxHWxPC() ]qbij91gDqS>FKYCv?_ qZL]qk;S4{$0!Vu~WcʅQ}G)o]#ul׵UO;e(^37VRφ'Vˬ;;=pKR,83fy`hǁw/'(PB0n=ArƯulnb0%iAr|X>DԼ|f EbL*vPS-,n[5/&SKz}B !/+ @7KlIv>[1&医5?A{Nkâ۞MQ}0yͽw#}yKJN??6m`"G=)' 'FϨf ă 1jP֘EpqSC={\2C@N8>ĩl*\ TT"h"8؜ix?S:}Z cQ9M:G@Aڪ|{iK5 ɇ+9q,͘{$~x/ْ(@yIENDB`twinkle-1.10.1/src/gui/images/auto_answer.png000066400000000000000000000014511277565361200211430ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  ~tIME;IIDAT8˭Ke[Uףi3:\ib@ Qƅ(Kŝ[Mv"l.cb1>hLđ~;==\;)@|A?kԕr#L֝wZ&itr0"ʌFP!g%6|Z>=r6@kֈ(HLLz>&[64kR o!K~Au:8şfT~x@>fRb6}y+ ^5.c*k) aj'sv̺sU$MPL{ae^4Zֹ%OO~[wêfcԻ2l%qu"͋{Mrt5:mۍ;k4ӇO H4vs%%3Kfb;ڟn^(5Ƙv͵Cs tIu;6o..-9x7LPzWZrʤ7ֻ?$I:VQ{|с@%]#OY]~]Q(V kض3Myō{zx':Y2? 78j(?Ķ:X.p|IENDB`twinkle-1.10.1/src/gui/images/buddy.png000066400000000000000000000013411277565361200177210ustar00rootroot00000000000000PNG  IHDRaIDAT8}KSqƟםM7]n6'bhP # @z 01 "*owы`Ė\Uwί+E{<|>_`琊7&h1[}G.ޝ>y֋,^6X8p\cS+.3KB?ݦjkwHS,:":,X⫛?m奚VeeIe DyG0nu+=@AHuBqta  A\᷊܏% 31D=7׷}^RUe 1vNezCJ B Ài!W$$A)%ZKUY f,#kJ*U` \3 w5ne-25M Pؿi&3C6f´Tғ9RrYI40ہ Tsl.k.gG\aGA&tw ǎ ]?_dDPՈQ,?G"OE9"zg6Й8ZJBt=J637gvvr{IENDB`twinkle-1.10.1/src/gui/images/cancel-disabled.png000066400000000000000000000010031277565361200215770ustar00rootroot00000000000000PNG  IHDR7bKGD̿ pHYs  @AtIME5XIDAT(EMKag%Ũ#clC)x[i/xz1ޤP ЂBA&ZÊY[wgZ N!P +(oM0~3_NZnֻOp*G:LDe+~A7|ae6sy[q>\\q]pv+Vn!y\RƝ4|,%)ׄJ}AD|sv u8Ͽ`>;Ux̩M%rwfу6(~v/NCNhm;L>+ǩ0qx G}><ݷZEFтAT k5_Yoڄ .B3e&ߦ}a&B y#t_~?~ɧWp2;+՝:Х"iG&cx;1&X:O]Z, -7b 07ov G/7W#\weSY"W|i@(dCuH^UtmleA4{}g |xu~l,_4 gk5Ȭ*8>utS8TڞAfя0?-;vt-ѵs E2R)X8w.S[Ӊzݙp:}ݍdׅ$dL&S [SqW65P/~-;NĮd2ʆo6:+՝.GDEs `1X_&јIENDB`twinkle-1.10.1/src/gui/images/cf-disabled.png000066400000000000000000000006711277565361200207540ustar00rootroot00000000000000PNG  IHDR7bKGD̿ pHYs  d_tIME.ZJTJIDAT(ϭKBaP̆ "QaAECCKBtí&jiuhbK*^*(PJsLzwyE} h>bēRDi  t< &^涾)`"=l.VK(@m8HH>Q{(G|$+{Xx>`5la@/QxeQ@pQ۵U\-:PFU%+% Rj(Z4S]|H5FڻePT$+CF%i3Fݢ#UFX1AR_u$IENDB`twinkle-1.10.1/src/gui/images/cf.png000066400000000000000000000011641277565361200172050ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  d_tIME nIDAT8͒;hQ{wfgggfwnVP4•DETXXXM"FERX(!ME K3{bIWuNq>klDe*PXH0ۺFďiPƢjZ(y3Cҹ\Ϸԛ!M L6/16mHPl ,]<]mܙ-^f<]~ 2q0vrB9ZhCW) E̬~z.}cLQׂ`gu,!ӛ1b]lP4lh;3N= )znldz=t@)!{ `ߎZº,*t#*AHā-eEWpߚ?/-p!;(ׅ=NߦddɈŤyו(:@YyǨ_ȀHta\msmQLU4*"Ĭ#"2IfѢ*VDz\8'|I:B䓻Whk;UJ M+ ΝQԲrBj"> %/- q$8d_/KjXZY/螚Lƕ8 ֯_M.pLr6ވeH G",D V3:p?fulzht]#` ל@[VMߴiq{غSCL00ThIGMAQnl[6iwWZ[3?ZQIJڵtt]kqy^- pIEqW y\ 7mYTB4WGwy;g@+VTw24_[t*ϋp.ZY^}[+>݁)6WKg9+"5<|?G2^Dg033icYe۶l9Teb;wS*+yU0G ĝy⎄7nkᮇ&#~"~ҥf `Wuuccq򡪢8؎"W|s ⿞'(\&"*q]h-@R6bR4rv^ڲPTh; _c񍷵W.\iɥiSTH$+㆔*j:g"맢VvCM(O0Jp؇la 9b0qLV?{q)\A.2B<$l7_"k+RiG߰˯~ @Fn>>yq380nS(tP( @/A)Z*\ yߣqS5v ;kV%0 յ!6ܽtNaEyyx)|p|4ԓ 2K#h ~A,{[iv2^ ɻ94ERM D5GJwf>@Զu5rG#aBRBų"cKȕ"pm²344m=&zҥqTTR~2EB/( Ypဠ,PSbZ wxDbC Sxkf&Kcb-S ZW)(k%a'0=pSd6gϞ< ${9;;iv!sFI ,XWtcaU]'=zgsR{GG jU=~C~rV0S>LP'>];&OJKEqg8 ǎu9lذ4 MFLq/`hl\8??L*5m=Ch:jo?E `eK33_rIahر@n#㧰sbuKCiā_$=x! L:ڃE6?̾h4eY7HMM%Ts YP# )E|2Х1&G'ʩA4&&D&9tGFΏs_xHg9]YYKq6k'q]`Lyx<Ʌ $3$xc߾bW_ {yxg}ʧ,le,X% #e.rxi/}'W(/[vh-[rٞϜhWR0/@i(aB}(`^RJ!RW]~[iIENDB`twinkle-1.10.1/src/gui/images/conf-disabled.png000066400000000000000000000010751277565361200213100ustar00rootroot00000000000000PNG  IHDRVΎWIDATx/2AEDnM LˋM & lAaf5XN`b * 7_rqU ü32Mȣ 0dP tLS\%j*[kavM&yjò,z I(e2Ͼ5EQftd&K!m[:#X,f)5&N !4S[.4 rIR C^__}Y@( z/8Jųt<[o۶Y|->NN'fms>ApM(J"_iZ|~~[a2n imB0b^ܻat8{jb˲8, 8u=tW#˲hd2hD\rX,h4$ 1Fn7XVdBP Lfe~#ktD"Pޚ`<{ldIENDB`twinkle-1.10.1/src/gui/images/conf.png000066400000000000000000000005721277565361200175440ustar00rootroot00000000000000PNG  IHDRVΎWAIDATxm0 F*67D*i șB:"LҤt p@DL+Kв5?}>wئVR\Az;2Gw T^3@Efuro? {Dkyyd HZuꆜv؇fP^" `v߽Ti@^ARy,e8~^ELKA#>2ێPY)*e{w+-@fh 3+QYqygagزV}URR:HIENDB`twinkle-1.10.1/src/gui/images/conference-disabled.png000066400000000000000000000005671277565361200224770ustar00rootroot00000000000000PNG  IHDRFbKGD̿ pHYs  d_tIME4I\IDATx!O`_Š!n,0|BPh$rCD"I5 8SC @xGHXW`8uX~Z6cSST0#F(]Y͐=#\(-JڮZ%wW{M4+"вg>Bw@,xpZlU@ }ȍL!ThV3q/g]fpeEQ=#|m9[XȢz@=v{q֝@Z',LǑ /Jρzyg?TN ]}IENDB`twinkle-1.10.1/src/gui/images/conference.png000066400000000000000000000007101277565361200207200ustar00rootroot00000000000000PNG  IHDRa~ePLTE}}~||~yyyxxyoopooonnonnmlllkkkjjjiiihhhgggfffdddbbbaaa___]]][[[XXXTTTRRRQQQPPPMMMLLLJJJIIIEEEܤtRNS@fbKGDH pHYs  d_tIME;? +TIDATxu @EљZԅT>K`q``I 25{Z5f&ݤJ&@'^D%gثik&_ϩ9'gIENDB`twinkle-1.10.1/src/gui/images/consult-xfer.png000066400000000000000000000014611277565361200212460ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  d_tIME "IDAT8œMh\UΝ޼13!UltT\ՂP(Htv+7f㾸.DpaQX `--V̈́43yoEh?Wq>׭,W{)M{εgɉmaP{^XhƇff*wלd=I^gDO/zק^~wdnob5Fav̧Voڞ}Y x1k|koNZי.ذ2]wnY -__?g'|X%L~VQM6&6hNUoa+ F+}pLTfN~%0P <}V]kJ"`s[OOV~sEc*ɡ$Et8+{ydy脂4ք@J O:*'ޅhWSV0H$_]8%.݄a14* 1i-kI~ހ89E)Ź+mIYaz^Ͳ8z rɚǻ_.'ib;ؾu1”ul 2+`7IYpFQ_ l41JYXKkc%,$3ߺ"@`'\&ڽ"\ؕn%k?E6`IENDB`twinkle-1.10.1/src/gui/images/contexthelp.png000066400000000000000000000014371277565361200211550ustar00rootroot00000000000000PNG  IHDRVΎWIDAT8_hWw$MB V+m' "l eJz -"{PuU(M>hѕ8ZeZm:Mi4wZ=<=6gDsG*,q*0m\˥qj<`} +MR wP]49qF""񐈰v~+?|#_e9'""hFT%}."mmmw>[ƣ$npuUAo#Es>TW+PGGǾŢm[ xHy ܺ:&,dVƟԁ X,uxx1l&5br~ gDz?mpNca 륫rll'Ҫ~6 ޛ|. U{{{`|||@DS Y4 15Q咇 /8Ntp{S@4%kwԄ_-B}-1xyj5K>u,Y՘w2O"lG2L39x `m8Hd :3G GMqE:T*C\uvvC}r:Z(M===FkEmk3ƺTYF---EDZD\uwwD)EcccFWHkojj:.@k>(=l"B85ħ֪i IENDB`twinkle-1.10.1/src/gui/images/dtmf-0.png000066400000000000000000000003431277565361200177020ustar00rootroot00000000000000PNG  IHDR"":G IDATX1jBAу6p n@ Etb%VػY+ (i+M`$pOf!Z^0G aXGT:d[-NUU 颏C0(Ҹ%'=zmcMSt,GD!RIENDB`twinkle-1.10.1/src/gui/images/dtmf-1.png000066400000000000000000000002331277565361200177010ustar00rootroot00000000000000PNG  IHDR"":G bIDATXα 0@я8 KG\ǁm@ Q+O0|$`ce`w~Wv2s!2!-lUoH)+H bIENDB`twinkle-1.10.1/src/gui/images/dtmf-2.png000066400000000000000000000007571277565361200177150ustar00rootroot00000000000000PNG  IHDR"":G IDATXOMa HYȿfVlg'RرD†FMk;S#& BQXФ 3y\۩w~y88yԲ{whkEduWaIczx{xg]67B< gūwbv0ދvzۨ8%~ Oq1|Jqf~/}1RR]ZdqIENDB`twinkle-1.10.1/src/gui/images/dtmf-3.png000066400000000000000000000007601277565361200177100ustar00rootroot00000000000000PNG  IHDR"":G IDATXOMa3JMjO Y"eM6vV6Bݒ"Y , )Q$ .]=q=ZoN<}<;V2fPn`,O=YvL`zYܯged \@ HL^b. ~XDFN*؇M8W&`AdcB))P1SIE_hiѿ맑qwApN+skpo"ϰ~kH%-2-8`+F><&7gFV,$}aR_kI| 7Xk~IENDB`twinkle-1.10.1/src/gui/images/dtmf-4.png000066400000000000000000000006441277565361200177120ustar00rootroot00000000000000PNG  IHDR"":G kIDATXO+mQ 5QH$P^{+0n2 f201I1!58v9αVOy~w?BAp7'Nэټ@px HPhm!@biFoF^tTR*ỏ6Ϭj,TP* UB?nܘ6"M؈ɞqorK,3 H6qi a.2 /ž,D'0'A]7S)GfKQ~ =KYAZ}4i5,A1*Tv (>vqʪ8:qCJ|R }MX-j yV^s;QsNK̯IENDB`twinkle-1.10.1/src/gui/images/dtmf-5.png000066400000000000000000000006111277565361200177050ustar00rootroot00000000000000PNG  IHDR"":G PIDATXֽ+QO,L( ɢ$$Bd2 *eTv)r;yU.=/%:;99?*T =O$MXR 9o9mi.^'> vTj:a6jPn`"Cyhuछug8դ"IaVG+D wB-V8=ZqU 3":~f7L3he ޜXui2o~ԁ6汗&QZIbF(&~4b  !&('4 WvND}ۅJl@GšP^`X|CduIENDB`twinkle-1.10.1/src/gui/images/dtmf-6.png000066400000000000000000000007321277565361200177120ustar00rootroot00000000000000PNG  IHDR"":G IDATXKQvB!\7BriE"D-(p'mhFZ.*.(jQB 2L(QY9AQh0afμPPpXEk= 2hd43hƗ ku<ÑX!C єX8:xsl hօҒЈyJiXGn/n 0W1ߘō0QPPPUS k(Tt̓YhjpO?Wvcd>&/Pfp mW]-f h|7|e'#g֙EuQ:.eRتV?l61K))^NF62?H6)d xhqMj?c"tY:TIENDB`twinkle-1.10.1/src/gui/images/dtmf-7.png000066400000000000000000000007411277565361200177130ustar00rootroot00000000000000PNG  IHDR"":G IDATX׻kQ/2Z@* j ;Al,Ҁh"i "HB Ԁ`Rkq6x;dS޳9kZkѴ.f4Ԏ2fa6rLT.gfEقS ei-DEEEE3j[)G />Aw^hf1EF㘔LG44Fb7ڰb0h aEs4Yo{f- "n*.nBr2-:NcYMz"*_mdUDIENDB`twinkle-1.10.1/src/gui/images/dtmf-9.png000066400000000000000000000011631277565361200177140ustar00rootroot00000000000000PNG  IHDR"":G :IDATX_hQf" a°Ҕ)"vKʅHvύv3Eseȅb-RSńHƿ\󶳟ʻ ~<=ys~y)І>DF`?~`s!ITb33蝈 ?ef6'xnT -) ᑰExxWh"9j1YDrX]hNjV80()4B”B("H18.[4'n@nc؃AKq|IJ`p}(;5V`[}-1y*|ÎTX#([ t_({+U*3kbpWq1%lU{ك(kȬm1e/d-mEآ]H)ۄ35oep$gviFV.PWBM*v Y=D߼vIENDB`twinkle-1.10.1/src/gui/images/dtmf-a.png000066400000000000000000000003501277565361200177610ustar00rootroot00000000000000PNG  IHDR"":G IDATXӡjQGA4  +X3.WWwFD Voh^A(j ||p+/s!n p!S,pB/WDe\!odj9BV( :Y.^P/!r=G<h_T!}к:Uv]uRńB3PIENDB`twinkle-1.10.1/src/gui/images/dtmf-b.png000066400000000000000000000003541277565361200177660ustar00rootroot00000000000000PNG  IHDR"":G IDATX?jBA?Eۀ!Il14ox @M|O\F6LdX>&`Aj*f@VY<*ЯKoWHS=^1:-ϓRJ)ߜ n%hIENDB`twinkle-1.10.1/src/gui/images/dtmf-c.png000066400000000000000000000003571277565361200177720ustar00rootroot00000000000000PNG  IHDR"":G IDATX1jBAУ]Tbc%dBp -&`%BRA-~!bﴏ B!P+8`) O|'t0Y cQ0k"D {x6Nx~X\ZXaHX_h _8ʛ4AAB! mqk IENDB`twinkle-1.10.1/src/gui/images/dtmf-d.png000066400000000000000000000003371277565361200177710ustar00rootroot00000000000000PNG  IHDR"":G IDATXM Aa'IdPڂmA؇lH)K003P+R;B!\q8?{0Eԡ A5PAA s_vȭ #QtKKc.|:ަtH_0$,׷!e40 3rC[c ߊ!+wa-IENDB`twinkle-1.10.1/src/gui/images/dtmf-disabled.png000066400000000000000000000005661277565361200213210ustar00rootroot00000000000000PNG  IHDR,bKGD̿ pHYs  ~tIME6 ,IDATxҫNCQuK4)WYh0XUg \MQTe m{}${=ggm']e?;AϮ;yUԞ8B'VxQG?DDVSQRQ)0R]D.9\>qSeᬯ8T3񦁖K%[I No20< XF=Pi793!n0S2W-œan |c ]5;v}N[bIENDB`twinkle-1.10.1/src/gui/images/edit.png000066400000000000000000000023321277565361200175400ustar00rootroot00000000000000PNG  IHDR szzIDATXŖKlUH4JT40QV&DtJ Dak\BưI|ѸVy@Z-P:B"$_&s~ϹU]"Ͽn{q݋]@gW29D: ܘ::L~ jpX 9s<S?>̝ &ꗤIƝ GrL@\S.W]V:6n_Q?a8zާ/Ҽ$xXZkL кģ44K(a~p+mOoõĭY{Q((X \eg?:Rs̅@@Fƃb#'*k"Y5 >$>CoUm,o]NPxt5$2?}Q@gz#< J2؋H${(uP cq*^ {w셏mj7zS=$9վ5.Tc$pUIDRY9B}WRl#{CZnZUވd,*:J1h Fnx1h-X@S osn^P I3ݧi}eD'Ѐ`e&@>@~xN ƊCڠ2YZZV‰( N(p9}ir,$- esob4(eVJAQ)%@˴If$=v)ʮ,CvV5/9IHT! #b qW#%*3Փ܎h'i I31QVIPڏ=-Jp" gAv\h4N`"y Ђ-ω*aP˱{iYJiEZ+Də=Q1 3ȴea~W߄JT~P I@u?F8YwQ`ۻqSRuiek|N7oDWikckT,]H~Vk Kr@Ԭr$ǟ`۩񔏛>dY2#?{K` dm;(B5ı%iӉ\J?|'t}CE-2H2m5&s|_tsbcIENDB`twinkle-1.10.1/src/gui/images/edit16.png000066400000000000000000000011741277565361200177120ustar00rootroot00000000000000PNG  IHDRaCIDAT8OA??t-)bHDLlԋ'5r2O^ w&zǀcPA% nwݝ!M&7e]hD1Q#RÅG›'3+ZuF:ZkQB){/(0H!Llp}(-1\ ꍣFpR`DlVn 3O=?w,z ot w*:(Mz:4$.aX460]bZ0B8vL.m=zLT;13kPnSI-tRX٪ʊݤЀCTIT388uI5J% [kT 43UŒ`]!AW}hKq-@IS~j:V#&8AIٔzȮ.db"5=%ԥIH!B`퇬/ }v40 >` (þ*EӸg&0 ۶ \uIӲ}3F!9|vC+H²,(BͭmsF" [{߯jTqmIENDB`twinkle-1.10.1/src/gui/images/editcopy000066400000000000000000000004141277565361200176470ustar00rootroot00000000000000PNG  IHDRĴl;IDATx0 Dψ6ԅٟPFpI*ZkB x@UL"R 5# UR·Q1٬`+ȇ?]x_$=0,}HI*MS yyX‹6[qۜM!:ʭĩCF{Nk,qgi*V{v?gSl*Qx~ߤ{0`IENDB`twinkle-1.10.1/src/gui/images/editcut000066400000000000000000000003261277565361200174720ustar00rootroot00000000000000PNG  IHDRĴl;IDATxA y6 ",^L4A>YyO+k]` DDdv+ˑɌcʟyf11tp p -hU2f^ 1\Vk}Vdջ"w6o%aq3IENDB`twinkle-1.10.1/src/gui/images/editdelete.png000066400000000000000000000012671277565361200207310ustar00rootroot00000000000000PNG  IHDRa~IDAT8}KHTQ{m -$($EE" jc MD@"^=:L9E30_;G/s1dFH&g= |=w[QJɷ.;:Nx1IO^877t`htޕd!RiIc[EɆu+[mY DZ'2:J(Cড়TֺOB E% `t$JWw?NC<>U_BxI (- /  ֡ZBaGaYVS=$bwPt,nk3+^oB.^ ;߶!P//[䱋X%KPBCf礴յ_3p?=$J&7*NJDZl1ǂ@( [ #04$ DhpKdNCsڲ.FJ]An(-/QadAbw[H\/D(AQ,?u(. HH V!FW)4j$h0!$LF7^n~͛IENDB`twinkle-1.10.1/src/gui/images/editpaste000066400000000000000000000004451277565361200200150ustar00rootroot00000000000000PNG  IHDRĴl;IDATxM ߘj7E\k@-Pҗ ?IDݾ 2DZ9 Q/Vj7Y@?<n.5?vN8I^ 3x B!Q9o*|2Gny3޹j+_+ݜ'J7\թ#Z:e'[hɠq_TN͎k-x7GxVџ[kͱzkzVW󘟅z 6&ؙIENDB`twinkle-1.10.1/src/gui/images/encrypted-disabled.png000066400000000000000000000007411277565361200223570ustar00rootroot00000000000000PNG  IHDR7bKGD̿ pHYs  tIME&R,rIDAT(]K[a{јބPT$PEۢŹK)t).U:"4K:FI;߽_/ }9>*._)AfT*oW#cϯG{n &瞽.O;oO~$!;k{B.iFuCpU/6\jh!Oo!s46fR˙TMLCs}REL <&L]ulGs/, šg6BpGA3!Aص5X@ug$oS?mh.9IENDB`twinkle-1.10.1/src/gui/images/encrypted.png000066400000000000000000000012651277565361200206140ustar00rootroot00000000000000PNG  IHDRa|IDAT8}OHa?]n?KK$ J! Qu%첛^ (CA,2#󽙑!p~>_[VxT$t,E<$o4Б ǢaRwV > q#5yeN£@ (*k^mjR,iA(N;P1pPt@`U4? G*VC8] 6T 0l0,н~#]E U v#lZ7. 7\ 6Bxr oe}pS'O0|e˧WC`Qhm](? ''e  ϟfؽ /oݻ-R9 :~rU}???~/ 1...NNN߿3_ҥ;7Y{ X%ϟ~;O_AFFA\\!8ؑ&&N@}8 ?}tWǏ=_OLLHBׯ V@5o-,8P7 "`ϗ,p=۷/@eAU{)׈AVVAZZAC$k@5?Ą?hi)˗/ bw Z;*}^۷O#~MMy&&V Y@x_;`c;8q 85(f@I>yrW{ 6}7.^_>|xRoE׾~V^^n@+PL-UE9A9D ~C 6[jeoH:7l@Lk @00k\`x-W} 4X>[`5%~No@5{vA/_\ Yn1V(_%Ш LVf|^99^Y9T?b ̚o?x(h/߾3䫸;1H(%PQSug%><v3h@?Ѝ+b9|~%lPq *3` | ?R?Ϸ`<@M?Pn_9^OXf `;7e&ƿ@33@Q ?@!3 @r  X o' y@ӿBACX@L(`aT# a5M ؋do~CbXJ !ņ`'|xGrX,2;=swaIceeͲ,386fXBﮭ9ztɣG$@h 8@&>1:I./wnYq7&D;ً#д!=iZrt:[˗s0mn8irc9ا 9j;r{ܑK]6K(雠gh,Nw#M!cB5HQklWX&>}|=6[jҏ*T?-AwJijU?t>UVW*|0jKH3SWvU۰@D\H^ӹm& .٨m@%Z6;÷\,h\w$jQk+[})˅ Jn.|~(&|H N#%9>OSf0ms{ d'JH_ގP$o]:>Bw w~35 _v"u(Pi50oIENDB`twinkle-1.10.1/src/gui/images/exit.png000066400000000000000000000014111277565361200175610ustar00rootroot00000000000000PNG  IHDRaIDAT81lWo[H. v F$!20w #+HV !ni넄qؿX]9:s'cnP*=̥Ry:~{ms<57],-рhQJ)s:zm2dS.s8=1{&Ov:(Ɛi4{" ˌ2i6(Ϟ%ekm Y]%Z=F̙3Lܸə~}2YRt\("XYAE{u>)\/O"/^p:ћĴ*^1u";aXP9֌ul?~LjQvɅ<{F둮T&9ϟgg}ﶷv:H.(}Ztu 2(zqΑAD/_F>GVpΡbkIΝ$h="8΀N&IW*|`<" ~M2ĝ;YYᏥ%rF)R0U6ZњVcKԓ'{HyO6#}pa*!$Ẕ5*n1~IƄ!UG(%(#Gr>-/-- uJA  2F}^]EbL$BjmQf! Z;m></4IENDB`twinkle-1.10.1/src/gui/images/favorites.png000066400000000000000000000100741277565361200206170ustar00rootroot00000000000000PNG  IHDR@@iqIDATxyU?w}{tݍ,ʪdH@4͌+ j)f"eL .SѸV*5cp&&nfޗ{59Unss6%q<\ȳ|63ĊbwΆ.g(3L4,}֭ȣ$p  q02oG@w9p1Yx]I& B\$ =iw1Pp@1";-@ ka(|t.0d8dXe@?zЌߣ5 T܀6D'\4[ mb4d81H0ˌ>05@S| ]s+F Ds ' ː|&RQ鍤/ž~ Ĵc\ cv`$vw F[j1Z kI3,BYjU({Ir.9@ P|8dP$ ]4f\dA {ﺁDu5 &3CzHJhI6aƚ1Ә c'f.:n@r AgF`yA ̘ ]\lA D} t!*?g%i-p(Bf J]|uAٵ>}R\ >~+.Ļ^^ `B%|{5wnt'D'*,U.@n+nh$ ZvlvY$]ī|NosSJo~ƭ*nfwBbBWYϲ')EktՂ 28m MVn޻y;-K`OXUUJtj;Z @xJQ:RDPb&mP2>;Y:+{&a04PRr\kAp}kC`CR.LF+$7oU/Ner#lDUOڻ=aH3I߃'b!YVzG # >BOo#Du*\4MAPӛ\G ?B'GeV2!%C aZL^Dj\D"рX 'Ǯfڲ9Y7L~."߄n< q4ŵz^e*xmZ_~M@)X:o>1R%9i m4^|.W%76ӨAIñ@@Z= 4vAqDd-Eه s2{]:+yTh_TAm@~$% d>K&( ;秈5p9'$hy!ʄnI )MZ̓΄@Zp Ip$={vA,w']nݽlw2}2#wEGB=ԦB2>,wl"zM-(i֭<WXPS#d8q"tʫ0s!0N p d (e\sV|aD!tŬ,|8*Tac] %49 ;OEQ!P0EVS)DC^_yV|ˈVA#Y@-LMraT =E'DX Bѥ* B Gۛz3,UQG@Y$ٺc}jJϽ,WWrEbcibu& &"~(<(B tEK :DӴ`}Ane 9;fg3'UP5 mM5܎vGi:xUhB:~Ulk䵻7JWf⼗o$*x`Y20"lk@hx:swhI4͆-/CM'a!ފpV㄃hז3mY5?/SYKix;`Y-Wlcr œ8@չ@$lJt~h70;197m\V%"Rnfڰ lr|4<9!!{7ɺ{,1sK5[uKz#Q;pY!)9-?Icqޞ.-~d"y 6$mt>Û+y u֌ [(*'ѕʬ;4<</FВCrMG*Զ-QꩶAgj_X"KJNXv$,X Y1@ͦTD`%ۈcD>^m-`4tQc- MVu15M$RXz5'鬮\G"%:bF/ ӴjdIENDB`twinkle-1.10.1/src/gui/images/fileopen.png000066400000000000000000000014621277565361200204170ustar00rootroot00000000000000PNG  IHDRaIDAT8KkeF}\IISB&%H%\Q\T]T*(AA*A"41iMI2L2%3彺(s?#pb?c=ܾ[`!M!VЙٙmn5/{6c/iı;ܫg.9n|ۍ R tH7JO rmk݇Ό~<{uؑQquzZA`8<;}ڻ4m`l{o MEjYp0p;_ , ޞ<:&O5AYvsILIcyعT7OC.Y.e ' Nt`LҶB@{QkSY~`2r$$C˦ wH.F =0G=v40xıZs[+.E:멷Zj'RCf0rYpg)xj4WH2'^?E/C8Xwoexc"<38Ԝ iijE`ۆmfuUa31;@2QQۑ^DD.ٷqWr [)L0xQbFډ ݁!U{B.ZYqDRJST1(CvwG?̢[pIENDB`twinkle-1.10.1/src/gui/images/filesave000066400000000000000000000003461277565361200176310ustar00rootroot00000000000000PNG  IHDRĴl;IDATxՕ1 E?ĂŌb4s -w 3 bЄ |A3 485̬Lih{] :,>%Ml 6yKRKdKOv'Nj蛴n퉕y'fVE6+ȴGIENDB`twinkle-1.10.1/src/gui/images/gear.png000066400000000000000000000014371277565361200175360ustar00rootroot00000000000000PNG  IHDRaIDAT8uKh\U9wfn:MbLZMmH)RHWbE"DQT.b)pBjĚ" ӄ1k9WA(_m~aɹۉ7'~t߹@cDcͰjgSei{;Eϩf֩4uw:|Bp^m{>}~oHѝ̋3UaVDOs?wmɫQ P Tjj޿:rR cM&x];Lf3P.Cn#tWɘ3z$=5aϻ9"h]bf¯ߩxr6L@0H4ץ"L\j2A;]LY[- wjݶڈhrP#s{zRS3Nx金}fnSl`6> gZjsCJ 6OM=|䫴:<8:z# '/u=Teuɾ@yCj˿Y1`)IENDB`twinkle-1.10.1/src/gui/images/hold-disabled.png000066400000000000000000000005111277565361200213030ustar00rootroot00000000000000PNG  IHDRFbKGD̿ pHYs  d_tIME41둷.IDATx1JQ[]D{ArA  KO x ; SAb3İ+1D|3 KtwZ>Q]WƵlR͇Wn?$\7iMWQ z47eƲ :S jX@i[ aCh2e-;$j߳"cPXi;UqȋٍU#@mKv#IENDB`twinkle-1.10.1/src/gui/images/hold.png000066400000000000000000000007501277565361200175430ustar00rootroot00000000000000PNG  IHDRVΎWIDATxOQvfzZFjZcaABB"R X۠VM([dpyof벣Nrܗs=^Dϗ(oSŅi<^XaH"mETS RXVd||=#0<+l8X~_0tF=˛Y̪fSZ!ڣp{&ˑԷ~wH@ed87M%ڐ):6h&Qɲ4ȋWFZ۵.lˉ#sJ%ʎmD?tfb,1n@e}ek, JY(I8<}knݐDŽ:ָOwIXk+}9vblrN\ڭ q-IENDB`twinkle-1.10.1/src/gui/images/invite-disabled.png000066400000000000000000000004761277565361200216650ustar00rootroot00000000000000PNG  IHDRFbKGD̿ pHYs  d_tIME5DRIDATx=N@g P%L5%Rr i|葢t"?;AWξpR\TMj؄@[,dl6Nއ5<U3qz1k{v g%ʮlv/L֗s᝾q;XxSpCmCtjV љ[ZUojmUvn/_4IENDB`twinkle-1.10.1/src/gui/images/invite.png000066400000000000000000000005221277565361200201100ustar00rootroot00000000000000PNG  IHDRVΎWIDATxM0DYĹKـѰA<2o LpvB8I.טa7GsZc<.X ّ@Mp-^#۳Ѫ_F$V)8JկYSUK=<:TA M ]WrIq@ߕ;-( 1j.mqˁw.dTQ]Jw= NC[lg_7 Q@a Ҽ3ux.uҾ)bLS<5~ɤIIENDB`twinkle-1.10.1/src/gui/images/kcmpci.png000066400000000000000000000045441277565361200200700ustar00rootroot00000000000000PNG  IHDR@@iq +IDATx]lGdicPCƤ iHQCUR!RZiRBZ(}h#EJFm$e2`\{ޏُ3;wf^ثs9?瞻3)%wTUZJ2VҲ @,Pi*-TZJ2VҲ @Tϑ0q@BJk@ H-Xj{t'&4N\bKII4) MYOfBH<( |'ٵ-u5K/m{+:.wUp[!~-r<\ήG#' 9}05[WK:k-40=BTJ~ s!}mžxx<,Ukn"z7>م"I&Q(nmŀ/*2Iy(YEzhw@#H044D,˭ |w}ݫ<)_&RQ!~R(")Alb~VNJxտy'wcùMlH$ A{kwǎ2*2;v]( p7dԈ(>nN2T@dԔRȎƦ>  pvxUhqeB܇ЬBuDCPUf6Hr8p uGk})y _Qh_]GNsM/0@0vas W)1ĩbwpv@12,?~dY e-:}ٸk1@ Q.T??VrB5' U6^y. ^b||\qL!.D 9,J aj-%lŊCzt^ sk;tae4M#N{J.&C.H @2iaLoNP罯`!j*!WS_[9z2[u6X4i{2j6*Թ S\aX1I |'>>r`Pmt=ixs½LpnD:,ڣ2w](5r$n3j=A%!]y\&Le#:?_~0P ^]9/ A~ S(kkliŨc!ŕ'@XƸyVP0! 74sɺ# ;Wٷ&koi)s+'B>}4m'D }EjbJ".F+_lY_6@*~klP`/\ئо&V澱y3vP}soW?Nsn,ۏov_ϾO@q @[Ì~t Uӭ8;{|Too`wA?љaN'}gŬuǣ/"@|( ZG aBvr%aRDmXij`NÄa>*fBUIm\0ZAfl ͡k%JY`ߚ|?$p@{?[IV_޸>'Y1F1򝚠3vy?r1kwlmlqC5 Ҵll|gi~HskL\"_|1 3Ӑn /`A$دKH⿊yH|hY Iij6 d)C}3M=i6LΤoRz3cۯ1iWxq~{V|wω`%8-2֝,.JpZ*ep~?ͯJI@;EǀRʘk̒)Xb@-ЌI,> _p]2VҲ @,Pi*-w=rLvIENDB`twinkle-1.10.1/src/gui/images/kcmpci16.png000066400000000000000000000015041277565361200202300ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?% (DIJr啗o_h(ß @@/?D _֭s:*Uؘ9adw//1830p13po n@AJBb3  T=K2ALBK %٬/>Ǐ/ CaWό?ޱaɠiʠ݀A7 , 10o <\j $1<b5#~&1 a7N @,a231y&,wj ߘ00rs1|AWALOA߻@, ve R _0\8 ;/wx33X@ E 5ca5 g'pex[E >H0 #-) 1ߏPײo;#3;@L [of( 66o >

zⵅhF3s;/rc;*OӂN%:nt(\8$c1&c;o.4'eޡ QYd0(@<'5Gs)5to3u4ätk(?[}oQ*D#MS 0pSBfyBe*` i8GO eI@ĤP6UB6@4qpT7Bd2Q`2l)W.#+~hL!~Ӱ.N8lψ4 PhEeOr_O냕z13 kb'@Ԑ V2“9Xp‡ ̇ ƃd.>D[|008%|l<`:+n*qYa*CbLv\2?ep`W,@:b:ObF 0`I/)OS.4$& .d]w`<~"M0? 4rE^e9~&OAݚE8))/|uA]7 JY!: Ar@y̙B/IB\I??ak׷ x9===d0GMӬ,KVUUU4 EQPiZF~7ٵkǝ,V&#%~ѱիWBL&IRXeY1vu !2F;w>>9 ~~HlǏm033C2,YUUu0|x<TU%qη?%=98::ZN1MaY5>:"czB]]]]i.k\Q޹n<&8D*q7ӥ9ijjb֭A*++4H$B(" DuǡO)tWVV4iii! ( ofhh4`ڵw۶ ˠ Z@Em.Hĺu룼׋eY$ RT៺:E!LXd\4dYfffr%_^DD$BK. ͛7|ض͚5k$d YD!0~?p>NF/|"L2<oEcBL4f38 l `Xǟmuw{|-T6Hjv;==9{dϴ}сalƴڪB'xLJRuCF" y*/n HuAX_ƈ;6ehBʪ6O뭢jA66:4$0cumϒ= ,Σ{{!衶j8oWh}0=ko>>ڡ'@h_ocMZܸ W̿}3 $45c_Hy\ rቇ+ 0O:pĢ(ڵ 1. `kٳ { 3ae.WnN! ȎOm+Fv->t)0.@2PU-:7d 23H@ƴ멱fÖfbEN%FF 0pdtM-CW2ŧ٩L1"<́XPMA$K?g (s!3o-۶9H"2WсvV%'\,K|Yο+ٹ\jX c թ8[`VV4.,AIp0JZ 䮘0Գ^\omsX@͍g9h)Ō"T wn!;Tspg0?Ǝ??%w\GᝄrcjV[ٴ{ͱ{ZVwU0S2}2^:@1`jӐAxt-wvBh2e'YŢH Y#QUt΢aeώR UKHKn.w&w+wus{cZ|~J܊F?HENn;<}JoqE/"eU@T7N3˩o~&p6!m TWԈ %ݐ1L2 CG8sW#·/g0U>ae쀩N n$ CIENDB`twinkle-1.10.1/src/gui/images/kontact_contacts-disabled.png000066400000000000000000000010111277565361200237120ustar00rootroot00000000000000PNG  IHDR7bKGD̿ pHYs  tIME ;0e}IDAT(ύѽkq^r^LEi}(U HA.bwW 8 .\D)(h6J2hiHrI~(:ф[jm;U ߰ou/Mz=Q QN_.P(2.{/^{BRr _$/哈t, $ p~fBUZ\$§g;qńx*#L1W7/9&Ǥ?s-ESl4OCDj]p(r<-Čs53aӬ <,l2eCc""ʤI2l,뻩K ti£cWd FqW&Lt &ģc$woGm/R}n,db~fIENDB`twinkle-1.10.1/src/gui/images/kontact_contacts.png000066400000000000000000000015101277565361200221510ustar00rootroot00000000000000PNG  IHDRaIDAT8MhuMl$DbM4@[C-hD^"=j+"E x(, =H1"H!Zl>MGfgggz=.?<!'ށ/w|D l.驓U*PUm%BT(^\{+!<ل_=@= y( =>sӃս/x/*OL]Z,};*߉(D@Pe3ʛ-SVK'w5ZnC xⱧ~s|L;ޞEKԫ$bCP΃l\T宲7*~8jHlI!}ɝ=V!Ԍ{aO.tg}XX܁\Dg_K_#>\de\ң ݨF* ҨlÔ 3o3ug3F=d i>O!G4ҫy$-A{_M]}'䬫Ō}<57T0f6m(Zp ɤӤX]}->#ت5z-+aN } +kT@Ij룃tMlVֹ52I nk}eskG)ҟYG$khEbӁ*9b^u$HIENDB`twinkle-1.10.1/src/gui/images/kontact_contacts32.png000066400000000000000000000057371277565361200223350ustar00rootroot00000000000000PNG  IHDR szzgAMA7tEXtSoftwareAdobe ImageReadyqe< qIDATxb?@b`0C= \ ?8~Û/&+ Ǐo<>v˙S3 _?2DO{?N|m9i1j Yo ƛtĈHH20;ϯ]:|շ-W{[P:qsY t/_BFv@7 .gacW@/( @OT&؇UKC*zA 2A,EPit@ m l ,23~a`GJu^`"_@Ld(4?_11E3Ł K$Q,8B `g l G[O` @0hb~ (6 v3ɐ B?a\̿ ? .""0{8@%0 ¿? 1 j%Zf?"C 5ԁAϽ; _6mlߴw&Wԣk=Ff fP c+*F:;PП@10 SkZ6?yPr@mB M'`п'{`tr{c~1FGx?r2%뷏@5@SS &=td `}jW ?0|c+S~d?~mq[` fe;б ;P TqgO t!!O$֓l`b d1 3޼|.y߁ bE ?. Jh(t`!(, B̚?~bx_YY;$gbT U@Bw@ RL3wˉ^frz/?_vf`&+O < , >(s 4i;? G{go?gTEӇ {o>9s|} s*T- ~6 5 g3G2E3ȿepS,a6f`1/ h ad|)C} 9 (V9Na5 Hs%uއ2(1񜁍Y122Lid`fKf֟ k}`B!n b Y gW0{EuK>~q2(5v0 ;?3 LXK2-ff`F"18l{i~5zXҘspӍs Kg>ʰ@9_Q<;@x#!aeqUiQVM 7-^GC??z ,(?fedL4 lݸ[ݏ >dz ـ! d3#!t0`? j2\BF@ew L8c?~.o g6 O0SDxX~` Y 7x? _]#lR@03xq!~/ -K*|c =& L gb@Y B}τ;l9$~ef D[qw,*sUagf&,`;o1|!>ggsw0 ܎ X@Zl`x-F l$ b.`s=$C_~'4Y p_@XP5>1|y}H@ALy[k6yH{f$( ,~@&,Qsoh7w۩m 5I39-<8qO3|Zh{ɠ{ ~ Kc@ˇC/3;7;2 bXߐ\; G 88[$R6z? _|ax} CG_p&j 6ЖF .+3/7 mp~oķOY@=0dRa΅IENDB`twinkle-1.10.1/src/gui/images/log.png000066400000000000000000000020531277565361200173740ustar00rootroot00000000000000PNG  IHDR szzIDATXWKo[Enǖr@MjcRy,A, lYF .JBRBL{}a1'YF̙=cƑ.URS^"TH9Y2EO>O?͛ j6yRK]ʯ:>~s@H N2PK[WztZ[՝և;bkCpE@a'm/bsFw.@p9z-5R-tj!_n-aj.z0 af&*]Ս I z9ŀ@w_wmcu37`$Fw*ZP6i~J0 $.(T=Uïaa磙{r8c拵8sI 'AmyV0 cģ,`B 'r~ ӯE1:K6dN$qWPM7`9:bVdC9Y&b!(a8+g7WafP2A:)ZQ bP"Zr$?\0#XXG~bOՐ$T8jE~Be$IktUKNݯޔY. i$S?\rA n Fcܻ{"0c|3 4A8 8E vv6~ϛ:Ћ0@!8C2NAARdX[ke5!1%sj0hR]uM]^;PTEqjfYA0ULN\Ƣ#8Gzi; rޔ\up 'C.IENDB`twinkle-1.10.1/src/gui/images/log_small.png000066400000000000000000000015061277565361200205660ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?% X@ĒGqr }? _~g ×/~w Ow0\x @.XO?iˎɞG`>:z̟?oN+{ _a셯_1|:?>6V~c߿b7?116 s QTŘ!߽RĘ=*rǎ5C!Z @C!;++#3#';!^.W@O?  @; 4Xr`G(a= .]@@^ tf`|i ' Y_?>'v?A O80‚ hׯ ?;Û7؁3 LL@W`@~@Ac0?Pd 4ϟpb/^axSEׯO@ Z`F_??#Çׯ} @_7778l (Iׯ3 `2  IENDB`twinkle-1.10.1/src/gui/images/message.png000066400000000000000000000007601277565361200202420ustar00rootroot00000000000000PNG  IHDRaIDAT8ŒjSAI'6mRCvSEUW>F{>o ..ԭ.qтSS4s縸 ҕ80}փN}L !GGܫvVvss++Kgj(%pE1Ix&h$ILU2Uvv>re""3f->C qzw@! DYgQb ^ N.7/[8>)7hO"N@|Wy52 h)9,;P5or=VicmaWJi*Kܺbh4-p!3*pw؂X2iAzC1GX^6CDVqN>s)ʵ.]IENDB`twinkle-1.10.1/src/gui/images/message32.png000066400000000000000000000016771277565361200204170ustar00rootroot00000000000000PNG  IHDR szzIDATXMh\U$mi22S~ )4XEpх*ZtYۊP`W  JPJM -jcdf޽Θ7IM{<%"RZ@<+"ocZ PR1wZZ߂1R+EliDߗR|KU._8x$oK \A.g+aȱȑ;Hhѻ z֭$=33?:٬an{jͿdr,,q* {z6y{b|~~lvM5"eK/WLfmVƪguk?ҥ9DP|~Z(vKKKCj"PXlHazzm _h^} 33,22D ~exq૯Slhk^@h\ 06Jտ kWk"[qU BcύH3K.f׮aD*ܺWDbt4N_tu¹s)fϞdaptu [9@o7|UyQ.^u4jߝɘ{^[hk+fRпY8u'\7iXU7F<[ѷI CQ. |p $j⥗wHx=zHX; Lp©ڢؿ7G:Ç'R!kf9|GHnOmM|ypHlԆH ^w/ո&>u5p. >"#JD:Ctp`oGGk, Vs)mlWEJ屔 [\1m*mR~imHjmVʝEbM;N'\;k{ JZ)-1y@g}R.IENDB`twinkle-1.10.1/src/gui/images/mime_application.png000066400000000000000000000017061277565361200221310ustar00rootroot00000000000000PNG  IHDR szz pHYs  gAMA|Q cHRMz%u0`:o_F [ܣc '%R;1@8XXae$ a%@8˗/ ,,,,mn!C0@(2ϟp  X Ă |Rb< ËW`2 Ő]PJ',]D v!@Q5@A |}}@1A]^R@Tw QQQY~ό3W >@?~֖d> &P/pЃj A h 5=@QDgO__VA PJdCXI _f[v ).=߿SX(NܵkW(=@,=Vj嵄,IENDB`twinkle-1.10.1/src/gui/images/mime_audio.png000066400000000000000000000036361277565361200207330ustar00rootroot00000000000000PNG  IHDR szz pHYs  gAMA|Q cHRMz%u0`:o_FIDATxb?C~~EE߿r? @ (H0x)S4)0Ā_ft~@ ٿr @}9.th X` P~r-D330 po6pг032j ,и ^I!  YŒX Xb!/>};_ dg``eeeXh8* wׯ3< LLL p gda'42Bb9LQsɓ'DDDAx!×/_sss3ɁǏ֭[Ǡ`hhw,,,(!@pW2hhh0;w j3Xݕ+WtuuLMMn޼ v=Ï?PB^| ‚a˖-x3Ȑ18CAܿ ~~~ /^dt84}ͱ 8|0>g A s!!!0 ߼yoPz8}4%薃@tgAbQQQ` %HPZHAR9ݻ Cw@tӧOAR4(|J  V@ْAAAPYR2`Y _|,+. J/H-rppia||| (zX< _z>ñP~=8ϫd)Ȣ{"0',@?yt@d8N;fȀ O:Kx} ߂rH (m())b(v5ldw姒CW:(qwT3DA𞄔`w#bI@b̙ðtRp P 1P).AA=z4Sg---ŋ3lݺ\8PBP9JeIP0}n6  & ReQQQ 'O[ PJE(9 =v[@,|.߉ +W2Ν;9APD Jɠ DJݠ, +@r(JKK3={!==N`A ɬYg̘@D@A J42c J堢s` AeJGGr Pi2 d,'(Tҁ6ȉ`吖P˟I%@dM/5@3\|n9 9*j9!@(t`TbjM`Est@(@nR@Y T(S9^A Ash`O &<#0?9s%P ( Fhuݢ#ԌTŅcsIENDB`twinkle-1.10.1/src/gui/images/mime_image.png000066400000000000000000000035441277565361200207120ustar00rootroot00000000000000PNG  IHDR szz pHYs  gAMA|Q cHRMz%u0`:o_FIDATxb?C~~EraP0.02yPA8 (ٳ+SL 8##{aa' @lbAr7LUx8n o?3AR |b/Xo d`60!r@? 4P_123\c` @,.iz/0!b wf@gca3BD3`3hL QĂO: E23 t( 0M7U; ~@?1@@jeffadˁ@ '. $߀3y@$"b&&&`  R/A 0KA ```ee@8/666aPe0!;Gg ?~0y .\? `;vvvOhp o}dӅBq7444o0 B/Ab GqqqC+P$o`"f`x/f/_?30|W^^^YYY(Y a/A/"" **vHׯ;rP߻w+0n̟K;;SKk׮Ct?x.BĄ+#cXp鼼bCf8x`8?PH0@(!0>qOڱuýO $8d2?ZhE.S@ :|K' 1Pao 8883psqT 9www`yB@)  Wbp_M 1񋁛l(@VG@t总w2HHH e ׮\d 9& \ q(ò22 DXhfPpJIIs E8Ai\`KA9!@89a G%,ŠT4,YX<ҏĂ- d ' 1 += Β9@/i@A!rzA@,u75Ⱦa AJȋ/-E'ٳgrRHDv `ldˑs`H 'DPPҶ̙3#f̘@((G04FC x!8` Pz{ 8o6秨^@]A>ʷ&Mr ] lm>J!rqJ ] z?=[ZZN<r lqLih @(i%:'NKpp FP|^SSSDoVbXR:u#P2@`S 7&P`ixH]!X |!ƿ{IENDB`twinkle-1.10.1/src/gui/images/mime_text.png000066400000000000000000000023421277565361200206070ustar00rootroot00000000000000PNG  IHDR szzbKGD pHYs  tIME ,!oIDATxڽoTU?LN۱VB!1 10qaޅ+I 1$ a#?шPd{}{\ 3t*'yy3g{sϽD|>/adcWst\lly%TBwV1")}󿖢A9EDc0FAjc#F".kUUr '/Ɲ-H`f]apqßel[l@ R o^̯Ǐ2tKl2X |۟=JCl2["BIףTe;`*1Scö=^D\U &ʻ"03m[=N_i/5Rk'YIwkQJ@c6+ 0w s0o!: ,8ꞃJvaFmAT}vp:3ȽIC|MD ӗP9 NS(p:u8FDJ æM$"X'^20ɑ}ܵf9f&aA8ضeYlX=B8 v%&#z^O@ "dmkejXԝ`S `dжKj1J%R 0摚tc/8 i]Y4f#et:"+V00 3H1vyu-` Hկ : [ۯ,@SP\\ .~`npW0gb8J w111ഀ^ @ ,X,9={`[`aprFw^2 ,~ARJaƌ 'Lvvv  *d%K~S'7_i ,+1aWNcugb ?/ePJK ffUV1| k?A U8<7 o`(%\p*@1!'6P!+@]?~Pp)) @ϟ2pC/0))-5wfJJPkSB P A Twtv3W10L5AVN!/=ݧo xM`|fx l,tƍd9\FƒuP^V 0d1#C VSg;2\=w˗ y bbb k֬[3= @a+WgLMM3BF& M y~ _>}dg{.CPPCoo/8Jac0@L$AQJ (z`XCdA s?~Gӑxyx.@Z ǯ^ /f͚4sL[4@[[&M3(//0oGSww7rZS=  ![ u ?0x00H%IIŁ__O+!0 la֢ρv G @1at 77l))) `Gf qPBTս6m PDR_`ȍOt+_xJ/~]@`=tR+qsP#d @`$D/*zLv>qSeξ wA_`bd^Ӌ-zG+@1B\"@SC!t`ns6IENDB`twinkle-1.10.1/src/gui/images/missed-disabled.png000066400000000000000000000006331277565361200216460ustar00rootroot00000000000000PNG  IHDR7bKGD̿ pHYs  ~tIME D,IDAT(uKq?K^J z[$% ù!/hh@"2=<O =~ANpxjAee.KƸ yģ=hRCUfXb bHzx%LJ "b9̺ZgL>,3\yTo}ض  À88vgb{R |ypmFau]@Z]2z֮HkƘ`/F6sxEj4a&R .z|kS8,x>C8<("ZkwhCUUI(;XJIRJrΑs.6#;ДR@/ʣȐIENDB`twinkle-1.10.1/src/gui/images/mute.png000066400000000000000000000005271277565361200175710ustar00rootroot00000000000000PNG  IHDRVΎWIDATx}ё E;#&H&ԡRgԈ{gQG0SUXbVgv`cHk%Ui"Q~=&؏@XCzk {R c7x 0D4!D1"%ȸO@M"~^>jb5?"bz'39N;V@y6+RRX 2Eo13B$^*Z3!q 9g8&/V2UBМ{0Ueؘnh1 Kn(IENDB`twinkle-1.10.1/src/gui/images/mwi_failure16.png000066400000000000000000000016021277565361200212640ustar00rootroot00000000000000PNG  IHDRaIIDAT8}Kh\e;d1&&5IK۠RP P(,]G )\)XmŐFiS6&{&;:/+> nCC^q{s}Yu^{S}Z5{ࣁf*}1/[FM3t+:7+=݃{}^ΎiVQi. =ٜJYG"3iy]%YoҵұS~ɨv#G:jPqarBGU}p02ڕ4ɘehEd u6>n NXfnO-98+NstQUK_$3%؎ͬ`3=:q90 A0HJHȪg,Mc|n'Yo{ 13$|q)%MM44֜X^ ܓJ~:#P2!_|kqZ&3Tl/ŒF`kC}l擶ȝx c8v*_bF38NSTF"Qbu55F zO3tƥtd"\>:\ @JE,V U"xCzUv<ƥ2;ٸgcD"^/ E§/+B z2 HeR.V3ۍ/ɭ x3Sq{04(%5{0k9;nvX(&M7+Rѷ.nIftzIENDB`twinkle-1.10.1/src/gui/images/mwi_new16.png000066400000000000000000000014671277565361200204370ustar00rootroot00000000000000PNG  IHDRaIDAT8_h[e7m5mcCRg]lulats솨+8("@DSVTD}r2`umtm. mRo~*ԗy9-hcg;n%Dz{~s= m$oQY祝Sߕ ۷8vݱhx&D剃t Ju<X&sr'Rs׍iJWTSIS_;;ݦ8nӃZ.v$;#u-t]@8Pi}sUmwI~4_2@0jrP4bY ]NSAMJfDd2b*j5b(nlm$b94ו׮P(ĦXYevv˪" ƿm_ƉmO.SgϪƥ Ïa@Jp]$ˆ$Ubn`@61|upP Z)V]9Pg9Әw(FO1ټBҒJѺ`I樕=_:60ݐZ_:b{`jb׫5K6|iQWB(%Dp9a{o}nSYt.ձ1r~So$yX@iƏ TWGVIENDB`twinkle-1.10.1/src/gui/images/mwi_none.png000066400000000000000000000041001277565361200204210ustar00rootroot00000000000000PNG  IHDR szzIDATX͗klw;އwuBb' @$PU*@EHJ(E*B-S?QP*MMb'?֛]{gw=;s]픔JW:;3gs{֚Os*͛}/WO''wxZsϣFkMie )5J-]GhH5DQƍn燆 ҹXcW|p C,E4ᩧn#tCGRッrǎ d֥rɤ뺮M,fضIUWeiWdk}Y !6~DY;w d8ŗl޽_ɹnufY+%!_ql9xpxr(Z ,0:5ņ ؽ}W^3ܖZ,\FqIFDTE)M,eK вOst[__gbϯZ=-Z4q>^)RQ,Pض5XgTΝ[94yN;G^oP,8ITNq2lN& O3w/6 ~?xqöLhKˉLu1<<varmݛV,֘]]RZVgŐR"Dk=od!S<0ǣV %)hHӁkǚ^۩VB/Mk~,+=Lf+X.o&p ~t< g x|7厥ӓ7CמQt}9Vωu"߲@kzt^[{ǎIENDB`twinkle-1.10.1/src/gui/images/mwi_none16.png000066400000000000000000000014261277565361200206000ustar00rootroot00000000000000PNG  IHDRaIDAT8Kh\uwH&3L6%鴩GnDSAhMXR\Y|P.V$DjQi]8RL$$3sX,=~?kϞOv={աi[ǏʉF&8Je]SV~8 >h0=ݩ#G\=cG6:Ѩ(8@43gߚ9=<1{!H P0433 l'CUU RŢh޽ V.o T(:~.fvC)kri(rjHZTaTI=ߗo B2dP*:T+u} h/9@یʕ旿W] sG˱XeB at`״YRmݎVFvq@};>:!Fv|MƞX<(YD<;LxW4M|z}ZJUkuyµ5SWŮU^~3m=e ZbK?.=9୾V~uKO T:wktHK7g߈%!B!#96\c c ݅@ek, m0M!vN}u?8ڶ>apL\8j0 dBasAe2( [[[!PTpyy Izxtt[V1SƘ:c}rK$zRӴ/r4$/e6 v%n~==}[T?ˎt0InIENDB`twinkle-1.10.1/src/gui/images/network.png000066400000000000000000000044361277565361200203130ustar00rootroot00000000000000PNG  IHDR szzIDATXyU?w7Y`` AKjclIJ6iԘ6KҴM4hLuV% QZDfY3?uF/ޜ{~•Jvi754w[q*TR j Q>y t[sW,yх my-kmZ^ʢ ؖ&>gYoG}?w/+8,zh;~wbٓi*d vYBLS)R)өrm|7~இ:[q$em:Z( eONLN˱5Y*{ReReRrwCۍ:ldܗmm2U}J*Pl$I" IQ"{![㎛6{ MxcWHe[y?޴xab1&&)UjTju5 iu4!I$T1nܛ+G^%иO|_ۺ~m g΍008HZ%AӾu~#r&0t458A D YGǏ-:vzX&]}替5MO122A| iaBHy$IJ (!$^0'U_ju 2"ID׏u+zr0 pl<I$NI"I0LDcw?rrL+7YsƩ:GRg8MCbT$ HN qF /Hp,X+@vv55d{='\JURţ2;2I68& >5 ttVil\pmב@k,oyG⒋$=uǎ qy nɾ_ig{0r**]]gڬ1Ժ~'ut,u(N Cݽƍ+ŋy>RJ,$E\7lX/LDJXG{GCWl!4q\CaD̜a8臸4i*f4P4urXL¶@D@J쳻Nض,LfH E[GX,Eݻw_"D4P(Հh6; UfY>]ܼbŊUۅ(垞S/>/ 37QHG5!aIENDB`twinkle-1.10.1/src/gui/images/no-indication.png000066400000000000000000000006741277565361200213550ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  @AtIME':/\IIDAT8MQ@ $"9;oNSJEB00v/m\\7q)xu(aP*E>z8S( -(@ @Ews+F SH12lALʼ;p[Ș~=ׇ saMԞbZxD4 K8H XD׋G>{"u;4#<7g=HbxRXw7o)!Cf ߓ`rm~O[\32Fwz3+3Hx;2 ?1%Gz3Ϩm;,8xIENDB`twinkle-1.10.1/src/gui/images/ok.png000066400000000000000000000012251277565361200172240ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<'IDATxb?% (8Qc@7 @2@ĬHl(A!A X2lj okȧdP}گ? ?YB/oV qj $dIH6]'P"@10" H3]"rTW__WہÀꞀOS ; B׀&03$0Z11|88> F1  F " @aC/Gpr000r10 0`^1a8ph:P"A %^`` S @@14%$@ xjPM3 g 0f6Or n5]ex4ؿU |i X?=L ^5S(\ `N~` /, {b,HIflb47Ź QU!6IENDB`twinkle-1.10.1/src/gui/images/osd_hangup.png000066400000000000000000000023571277565361200207510ustar00rootroot00000000000000PNG  IHDR>aIDATxoTUmK*7DUK @ ?M\+j2qZJ}V .3L[;yyΙ'LӤy{}T^~`;mwğO`1\U?Ȼa>>> pE zǀIp0:% \VXGQ>~灋ti? ~!^` xxPt);׀!{d8tQ-]E I%@S'_Glw]'@;X^lP);KXHՁZ;6xw$PžP | V|} `ǁ=h;Φ1¬ѹ;rK7$q*NG;/Ǟ᷑-4pc5aLIDATxAv iO`f8W"YH_쮢X-RB] "E0k`ז-_[HHkB,e1nIYJ+SPRz >(uZ{9M#8|X W͑4]m|#8|@> ?bu_X |a/,@4M>kp#8|sr< @>O)ˌA_-^ `E8 {7ö́ѝ@  @ A @ A P=7KK;_3ҭXC/Ԁ- ?3оB";woaX2$IENDB`twinkle-1.10.1/src/gui/images/osd_mic_on.png000066400000000000000000000012221277565361200207210ustar00rootroot00000000000000PNG  IHDR>aYIDATxQr0ѐL/NĊ@k !Gvdm4&!O60+DKKMN|6$8 =`,f?HI 4W_@@@@@@@ cRr=[7̄o]K?W@@@@C}vl\H XJ' EK@Y(9qz=@ 84k*e`iN?DAy/c{tr Aׂ'k;#_HH|($JmbY*n&0wղ 8(G %S_yo9<7H=z=/~^46I,q[t?С \ v>5%~s(g;? BZIf<vGG?IUU7^8^p@@@p0U~t[*@%`w@lUN*Pb?W]5E_IENDB`twinkle-1.10.1/src/gui/images/package_network.png000066400000000000000000000040041277565361200217550ustar00rootroot00000000000000PNG  IHDR szzIDATxŗk]U=v0V F!-&LH4#K!!?JB 6X`5XH(iڙt^<~);Y9's^{-R?q:~3k"eA 7hirw$#z&>Jjv)T'%$]A$a3(ژxf=_K|(0R{;"cؚ [<ٰqW)Όu.!={='RcEהƯ IIJ(7A Jqn"`:o]>h4@,L08#MBC3ئR ,̕_}a wli(C{DqVX t*(C?7L!z|N!}I#Iݡhp3!n HP 8 X~j=9%o|la(0?`ftߴIjv/DB,AJ^*HHc*B{w?0BqQĶ7˛kqMS1) ^h(N4@;%v`a-D/n1] [Œ>ca S!4|8C A Q ߂tmMj ԗXzbBOlAb0߀rC^ @{G"tJ)zT\+ ^ N V qt=}5?5D3Yjs5@27^}y^Ic\)&,4+hs`-hj5Px/x8LL7p=@9H(1AS0;߶9U5u`J2 ^NW^'`uݟ\K\Ȭ6Zi #fC>=ÍES:A ASVh L{EP"5twa,N)9Jw7oe@/< L^X5@.Y1TN63[ @*HERDdRm8,ˢ(ȄڜGi-:/Ijo#[py~I(_N[$c  Y\%׻o#Wi)GO?Xۊ ]Mi@)S}&j .4[`{ׄ }}z %d gm}d:3ޱ=>Vul7|ѻfǿq:^,2L0MiL۲cha@VEۓH ;+gM@\e~̅ @' 9 oi˼ï|YHXMOow I%J[Tz^@hu쟏}I<0fB[q0QJ%Bfݾ897kj#HXi2QHq~rӣճg Zf~`Y.p_|#3$gˆ]*SslXf]opd?_^ydsR*\a-nߟqs5u@'t߮6o]g9 ?7sc>{tQK*:;ַi$"gN}D|zmW'hm&/C1zyΆYӖG=hukIENDB`twinkle-1.10.1/src/gui/images/package_system.png000066400000000000000000000047311277565361200216170ustar00rootroot00000000000000PNG  IHDR szz IDATxytTƟ;wf22Ʉ`B a HSQP BRRY (4D)HRX-DA!F(BD!H@I2?Xm59={>AuN?\8P\l~:d/^0߼_AYA i-gsNMMM Xl mϘmtWq $as]l$*9㝂ax{֟~姓TINr&frZo67|]:Ύ`݊w ü(yԳs M5}( aVB@?uO|os!5h B(-\\5>Ĵ_@hDZ?j;u}itxsGE;ώıf:>WW<&+Mcy`( \G+TU&QBoj jMp__a9/?;e*Q "AkxSfMze%"N0>e~v_)_XLDP$؄/mq:PqV!wW7uԵْ$ކ<ѣ}%\} &f_#w+UJm+duO6bE]ѸL25k ?xn֓3x^hlhs3R^yXxB#Y#>e*tmA^+~kKavYBnjϟ7>5>%~O$/>eKɲQR.qr4cB HرꖸhGKM-GجK&My-qtTE=dCyHqV }bD[?ž顠7١eXsPΕn96?{칫ܹ\PP mM"٧_6bGHp BBNnE c5;c"LΑ ޺2g% ::{p E/z/';bnY <%2DA>}:g2a2l}*~?ax~=9,<,@9n }t כDE`2Z0*7Aerv[| zpY2$f)8l6_UII6< Ĺb1lXYC s` zpkɴ4Uy},WQ!ĵ& :u4aB*mɎˡuCdھ 1ڷ0SjڢLKkK~xqӦo˶c25FEY8Y[wEEE-4McÆ,KI}OE 76 tsɐeESu_$.O`lӂS_Cb@,X2*rرe!Un22߀ l {|s.vryKh`X,p'0XzGZIENDB`twinkle-1.10.1/src/gui/images/password.png000066400000000000000000000063461277565361200204660ustar00rootroot00000000000000PNG  IHDR@@iq IDATxyl}?of"%(:Gؖ ʎSQ#-W M5Z+4-b[vddUrP2%RiHkv?,wISXxxowg9oOhV2M@(LPh -ZB4V2M@(LPh -ZB˧ X`KO2x3)v/I8/;kY㞅U EKCE-xv4#]U{JP!-y3uIm]={3tNᘎnvsd5.dvvk޸BhRJo{LYX(` _p~ZZ7V8 ] &9PE8w,=) !4@q$ l)|E>NIU2H 2 @Bk\LJW'v $L$/jڼ0|i>h xyH@(xfR)):’/>D/7Bo[xB'K”"bw/YͅvS@p@p D (/! x)EL y;k=iTЇGa fOH; 8H {!> /syys#@(Yu3Ct+a|G?ks+V! /,~j+;-iݰe VE$dzǨK?۹Yڊl>ǐKxY$hA( #'aRf6h<kEJhdžf3ow4Ob~/cVSðI5=k{%@[g ek楾v[) κU[LR!TKh ݮѿ& _'"] HJңVw fsEaWJ(\]q8gC/?<y,傠|bՙ+VGSUƍ[> a; ӶStžX>+gDeͣS}7{ т~YFՙ9~A!_;}i߰z}º^k=[2}*?,aXJ7 ΘCE>`Y934%$d!E™ ;kfzr'urg1zBڞ^<ޱ!ԀA':i:5bqҁ@j*D$d%נtsIc F dnQ(G[圹&AGwePi: @+ 8'8+6l]U`\(LFy'ZUm/8pxs;' FIl!GBtcDP̦;#:+uH2Msqm󷕖7wm\(n($%ڍ {?9zٲ`3A)mܤK!I;yMI)ַ̯Y/yVyHJPrғׁ $zXˎfk}vA%cq!Co|=t%KY7T/=>Y"&:pp pW/'~e~ %z6 $he/.}'V-rRvYH@t\vi ! ̂)H` Вڒ՛oU峭@LIı펮pzvkE(qS6zΟX:X3tB"لFsŶ ٪@ |B^e+~JPCD0FQΖ_g4 J*4rǞ?L>F5D/>0 pOko )ZB4V2M@(LPh -ZB˧aTlIENDB`twinkle-1.10.1/src/gui/images/penguin-small.png000066400000000000000000000017041277565361200213700ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<VIDATxbd@:::ibbblllʿ~bxGmM3 fgg|)7os)L@ 55K,>?cƌ@@eX &HJJ9߿eXh=Û?xãXXXT@ R@,HDܹs FF ߾`w u Ve Ei]]]_W cױ| / Ni 7$@8''ׯ_ oV~)3 (#߾}@ ͹@2 ?5Bo߾ "" ((y0aUȀ&%r@04߾ b_ ğ^lٺ?cW çO>~+3Öm;?^ze5PM>VVFSfݼyYAJWa?TAH>ÏO^?^I`p| 33==v[,exnfg N* ٘AA %# Ƣ36&!-gE,@pA7@|+ ܫl_}0IENDB`twinkle-1.10.1/src/gui/images/penguin.png000066400000000000000000000037401277565361200202640ustar00rootroot00000000000000PNG  IHDR szz pHYs  gAMA|Q cHRMz%u0`:o_FVIDATxb?@b$Qt77߿@ /^ @5xWUU-b'ï_0<~߿2 &&f 2<!c ??Ǐ(>PgpqqKy󆡫֭[XC-WG Ʒ . |pp?y?%$$322Ry .K'_XXl :xcN߸qVoolll =6{ JJJ@3Q$pݻw2ܾ}&@ L_y/_.ԃrP~ nϟ>ưg^-[0<} > ̒~Ç >}:Lc B X%}4-0a:3z ΠtW/_w0^n5ar3L 9 D4(@)zɒ% jj `20*|/xS:Gm 9Հ+K2> l@(@>WPPcؿ00`:{Pd`$:]ŏaڕxfbb`eee`aaa U@a ~X? rJT^3}q͛#Y?;;|||#@[@722?gY_'`zg?|hhNN󿠠11 `#@w0=X P T0Ą{E|e.Ј)Črطo(br @eB s<X(iP]/`" @L,OHH'DtAAAhàoau`L P6lb b)#??rPm1j0#Ç>Q7,L9`! r |n``0=Y55@wPy@A18;88@FFx ~3j [B-0 lu9 H|ZHL#p8 h4b 9/CFC!`8ŋ2U6kOA- ;7ógڵ96` ĕ<9Çw'doPLL|#89,K0GO|hqw:+@OW6(89b`ח ?fE-A 寁%`yAZ(ïϛ.;VNAS@3d[$OHA h A_߂< _3<`LV,50X #l* 3@=9]Y `pʞY@0+!X6 f\ 4?^~ax)pBST\ $s10=@Dr^ @4 0ZD61\ `zbw8/dX\,B/ο鳄=ɽϥDCB h@ l674W",rb5a0ba@g.10||=1lv`Kr* 6{m`m`?]^ꀻ,@r0ԥX_"rA҃~ @=0JEkiEIENDB`twinkle-1.10.1/src/gui/images/penguin_big.png000066400000000000000000000151671277565361200211130ustar00rootroot00000000000000PNG  IHDR@@iqgAMA7tEXtSoftwareAdobe ImageReadyqe< IDATxb?H0@ZPa@ T [WQQfC,% T# 3S{gH){ J)!PJA,Uԁd.(`, ,O9"@&Qg`r__= ###]]]`1HII1 <`L= u,0(5|aO>S?9 蟔f ";@P:f0Hb\K 1@XDDB$%%^z%@6̳ $e Pӧ,$->~@dzl 77 Q4,, ,H-FHyPj XJ$64(= 8`)LE)7 Xm <J:H:y  <``s{@03j:Ç:V#P ;w.-0Pʁ JLZ#.. 8$oii M@4(#""|}}*P@:u`s@=o L i`j@1Ɂ[q0 )@} ʧT@f3ɓ`= ,jPl d-Î;-DzPi `x-Dt_QCI0T *AڃUo 6#0O<,]DEElllHZ@٭PUUv&t50CwDWN à, $$$ Lf󁥱((` pc=A/pAP[h&8=NaPBn3 ,03LU@0PY-P  *@fKoMP| KAk σJ[2.] w{%5!{Ve&.\"0@#{޽{Z  D/*^x1T &PY r((0,a ʳϟg}6<\{͹>Ã=֑J ` +lA.`(l ! e:\〆Zx)C@2&9)az(%RH @֭[඿<0[ 9{A4(#O@ 5S5P  | `!' l8X֜A P9a*lmm6o N.vvvR.X-˗Q" `9%lS`@ o (YhId (A99PL5y $0089=V0iDc.04442pq o@Xgd+ß߿EP̃Rŋ `X RP^ @ _ X@U~@KA@9(@ѣ $E#ـ=ĄXio20i803 PJ*GMưo>xComS`S t/Zh00OـB)P`/P#0_zBׇwX`ee:Z 4 X..&`fQ`Yc }`x΂G*vpٳgE}X6 \~`Ui 4P +l@8k6V5:@Ila > `)Tol7c ZK=趟gg~aՈ%(0 819r`Ejpfgb(B =c`3#g PTUD`j,Y!44< RkL:~;9?p3|Go1HK1tl*oܸÄ> ԁ5>jxEay T:#N y 5@%:0@9e@v@:AEd$< _zs~2 j <\B;Cnn.öm1UF$//0Zc6OKyKRP1>P}*bFx';k DDX amP Tdff2lܸ`2H$t_`䦝8qbRР @1FsA!c`&c; h @Xrv h0󠺼PrEBƐXf`6 3<ʁ b:HZT"W 6n Ѝ_br q@K' bGEXV9$pjyzBn ,I2*878qANG@IQA $h Ϟ={yX ox=f@ <@s~=O?qDpwIAϓC2WᏐ7la]# J l;Q@ <QRxt4}C@!DbتX1Ƃ[e1n-@GyY -5A<ΊGJ@*BK  / ` K8_9AM_P4zv,`j/:F(C-_Ea PJXj @:w#10CAnnn9aDf#rGՠXj3@oPy1`MdE44P)@dm2KC6 \bP5`DIl1>6<  úbP5Coh h pC1 @&0`>DP 4 _0G<\ߣ<.d USP΅إ@>? h5@N1BCFN ",5,@@=RPCnhBb&ei/III)++#Fy1zqCXȥ=F ̣oAF̹v<AU'l=[| mxr /h+BBŋ|+0v>3|!CA%>z?+'ׯ?0}ӝ;ܾ}}0`AcC h3c31pqqI ~į_>U[MMPǿ@exH_vV&.6N6`o T:-Fl ,A @O33ada`c>^< %n{}>s+{f``QDlm=JJ4_ L?-}? 3=óg, 235;?]VvvxV;LǸg}Q]}i9A~&f 织7?w5EC"{w^ cC4F0>`2{?H/P X|s, XG3wX֝dxA >kDZ_~1p}e`b*/t ` g I >g973x՟wמz?mw_~Z(b bdg²?:WHDb9}8h< $z P =蹟@aff07@5܈e4v noY302AJ?hP@?o ?aw 2|~Oٞ?_^WAS f E1Azoퟞ ԋhY:赣F/a8p )bOGGn3sb?1_SE? RY؈00bHu !gxJɁPam'[Z3pQ5o-v>G3Xp~DoKw &/op.g_e*xi҂\+o|~ҿ`q j $J84Tkp'z*n@CEMED rH/%ݮg4;إz1ĕ*#o{Y U&%@f/16Hh*Z?>wTtzrTUD֐rK/ejjA>NqZP/g1`/zD2_s&5B]$Ju8ؙ9 [2_?a ǟ刺|>E>~?#-XRi"I,P@40aA Z| L>kW۪ܽS_n+F#f2#; .,"\A,~6CbǷ>_~vog~yZO:#aQc46abQa/0f5 4_??⭟?.~4?"0 V BuXŨd3\D:@{+a['eU+ sIT%dkK3B fhq4qOZ0±弊4= ^f:@o|:V=ǀf%DC&"?RLc 0b& NS~qIENDB`twinkle-1.10.1/src/gui/images/popup_incoming_answer.png000066400000000000000000000041371277565361200232250ustar00rootroot00000000000000PNG  IHDR>a&IDATx_\U?즭PmVF$" D} >h}dcIF3 $@6.nv3_ܹ}쟙9=s\.r\.r\.MI ^˳~6IYu/% ߗMS333(iUcn[KLÀ+Kw+5\Άxx(c13pL3ӒF'YV\OP& *#jR/'v3{%Rr aZ-)8~4k;P5҃"kY75~mf7@guˡuo0hpo'M$JV]q"ik|YFIZUX¿ l>xw;%4bȻ $ 6/Z6ws iIgU|-9X |iS}1I7H ڃo `~.;,jL`a`X_{?4 -GK[%T&w]mfӅs|vIj,B-|[—TA_3I2B:t.@ߒs9B9m`VٗzS/YK5k1o싸 _kn6si~m +FR& šH7o?ne+W2@k$[N{%}[+3*̞Y-͋9t+?nf%ͬ:f6&Z. +/ c I0 0oY鍊^t_SLϐGI^c,~V\+ڨƺPm;Z/i_ KzhwS*#z@DK]!`_$^<gfP2I:yaKE#T׵~&Lo(9w5I#~{v6LpLKzmhJ)jOZ "LpBFy%}+\<̭gϘ٣45T5ݜ݌'|2os\/$}~J-v +9;cR #!yF.7>[u=80F'2 ;lhdP#0@Z=DQ], Pkg\.%Yݭ'W5u;쿎19.ua(gP#q׾Z Aw~ǗavywX8ݕ$].FC_u|F LьF2(l:s/HwS:l#M722l ?>1p*2rt`jJ 12/#It[Z)ǒNG!I8$H]?&N$ {nlEux8DLcjIW%x1HVĦKI 7A:(nXvO)Į`oI1w3t6 gxo6Zݦ$ɞ-N IsVP3#o 8J>/sܝ1y9{X&i#oo *簮+fC"`7-iE#$8eٯllUǁwg!eEaC038Ec$4cW=DW' `f$ǫ99G'I6j(~J~ Tp) <Nrgl&~ofqڱVZ'OጛTr4T%9"/#]Gm*&8|7A *MPqnwTK5A jIzg%U? IWUKO nh80EEGT@ lݚ4H0i]gv\.r\U^t~IENDB`twinkle-1.10.1/src/gui/images/popup_incoming_reject.png000066400000000000000000000006071277565361200232000ustar00rootroot00000000000000PNG  IHDR>aNIDATx܁0@I-v+pNA~¿^2}[ 2ňKw"B.C]|w""DEKwa[yi[ .o|@] `<̿z]B!/mo9Kg^}켪&?;x\@PS6 `u^ `( (=~Bv7ʯe6l%<[:;Ѿ}u8ڜ`7\yIIENDB`twinkle-1.10.1/src/gui/images/presence.png000066400000000000000000000013741277565361200204240ustar00rootroot00000000000000PNG  IHDR szzIDATXMhTW\ "](b!-AB#B.npu-[ЅJ( bhAl$L B1|tM&..sιTUi 8 (՟yLcfڿifv8k],DYL#^YL9gذܖeeO=u/XůZ__$9hxit3C͙\ZC֍U;/`=+*{ϦH) [GK7W 0l8? 8i+<'A DbVq;գXVP;8L =zrڕI;ز^b5"wup22INn9_=D[~[>˪7(wt;:PhH[ t{[Wr<BuREe g!qrc&搏'HT.yQ>– ZqJjKq"_ cd L}Ɍڵ6b&SV]w7~0iSqvN"~B\vyEqg1xYWr/⛋.~Z6t!E\3yY}pScFoLbٚl͊[V=x]sҸٜtrgF͙V{>h0U{sIENDB`twinkle-1.10.1/src/gui/images/presence_failed.png000066400000000000000000000015001277565361200217170ustar00rootroot00000000000000PNG  IHDRaIDAT8m[hufg/&K.hR)mjԂ- H@E0T *(_>hmPZJK/i,6flK6w3Cl4m?9ps>6si[q>\\qke]<z3Vn"{\RƝ4| E)U}NymI'3O@ pkucTT~'O Ln7ȅޭ}@sڠa~:8i<X2C&N(chOdv.=}{+h-HqVm6< mɵ@ia '1{H$#C+wc~%\retHF>h(o<8ގv }LxAixDt@z-;b 07ov G/7W#\sJ%S"|q@(dà 5HNumd%A4{{w |Uxu~l,_4gU8=j5 tSbu*Cu JXGuh؍і;:Z-E2R)*X8{6s[SӉzݙGfw7"ɮ ISߒSe7`kF 2Jަfj?˅G%⇦˖e7<Ӎ6:˕.GDFƳu`Ѳ ?_&IENDB`twinkle-1.10.1/src/gui/images/presence_offline.png000066400000000000000000000015211277565361200221200ustar00rootroot00000000000000PNG  IHDRaIDAT8UK+W̽s3wFjiySgn fSuEtQUB]X^zu EI@II#QI&}Ggpߣ)P!~\l64Mewwb,m H=8lV[ ';;;ߵZ?94MC TKq#" CH)8󭭭WBS!4Mf8.z @<n#?)?OMM`0$g㨷8>>FEHnJq# C>]^^f4 mF}^FBH)qwwF( eYpk4P\.9HA))%!0 i0 (033a |϶m\^^b4X,bssRJc{{I24eYPJ>$A6m`R 4a6 @JhRb>])`!R B!h<mc0BGRT*M #@Ӵ=CZ,B+ u8<<c,4MIA?IENDB`twinkle-1.10.1/src/gui/images/presence_online.png000066400000000000000000000015201277565361200217610ustar00rootroot00000000000000PNG  IHDRaIDAT8]Kh\u{LN24 ]RvSJPܕ".\(T[ԍE7E+HZi`j*6I”&f:yL'3sq1U,~95 .Ԋ`:u3)[#]=~˧nܮeCD<4ԆȮ8? {@ߦRp_g  nl}xJ6 $]]f}x솳QK0-pjkV ~6$C ^ť\Z5NGڎ]grE-*o yps,Oŷg:I&ORvԲwBwx6J]A cƯ90/RX 1[`Oo'LOEA8Xḥ)V8@&dl6t]mJd{wGzk +#h w+E-eCw}B$ 8@Ti S`jMc}! @j3y(.C kpbN|xi{a5e8y NnDVؗCr_#'7ujyu䝷)"svwd-CDhG[?~cpNC m-p ꑍ^E`oɲ^5xb[n_߃PWM\lM˅@3F ^۟ju* {b<HxȭD4,K?ZC30CʴMnu_F#RQfq{dDIENDB`twinkle-1.10.1/src/gui/images/presence_rejected.png000066400000000000000000000016001277565361200222610ustar00rootroot00000000000000PNG  IHDRaGIDAT8];h[gŶlt$j*cYĊ3 MM]BЩ4CR: JoġIg4\ Gu994۟^\] O֚ RhFZu:uz_铹\B@bmkk%ėR;/F諳gg9vlB ܾɛ7Ϥ_\.A^)ֹs4!p_=41 _3d2t9ujvtvѣilVFmͦ a2cZyIJö!S,LM3<`p0ӧx^x:IeY?NRtjuRi7FC '8t(׮/lll8S2ۮp{b7\;0L"(BH뺀\h6E Qe3Ar 2jՠRP.ŋɤ X,˟0=6硔u~,k-(իf_e8~|(33oRLVKJݺ>pBZkwoƍi=vvZ VD&3~>uHbh('/09egg-=zi6gTwe |ynќ C!L8h5677X]]i5OT?(@~w?Jzn6[!i6y|.9N낔D&3s`"Aw: )PȴuwR`u߇?l<IENDB`twinkle-1.10.1/src/gui/images/presence_unknown.png000066400000000000000000000012601277565361200221750ustar00rootroot00000000000000PNG  IHDRawIDAT8uKawgFS#aFC:P@iUܹp^Z¶pmY,#$hS"[:::;-tF*=p6眗8ú$$BTJ܈JV y*} ]XS]lH4cjTRmPw ]\16˿ [|]uлRXe#L_GpDXZfAENxc 0Yc_N=M$d -8~G`;@Q#WP0*pa2, ڤw vE#0t Iȱl @h]20Q]E_Bрg9h39tCKajݽ-ucQ%\Cj| B&8FHeD!F#_EpHʚQC Kmk̲CbQx6G$Ɲ c[{s*^S9. Wnu8?&~lډaڧ%TҼe)Mbmܛp;iCwB.~+b<|ǂӂfvKL-YHC. !e1]̕2ekbڸs;'uIENDB`twinkle-1.10.1/src/gui/images/print000066400000000000000000000013051277565361200171630ustar00rootroot00000000000000PNG  IHDRĴl;IDATx{0 810a5 ,XZ?ac ְ bw,׳(.},Oʲhv/"iznf*!$97M/R!t7,*3i"U9y%%S+ܽ 9sG/"gXUImt(:b ]2^%uU=f>eUxov%IȲtXU{] ӹW-eY҄}Qi:o5J|y45b1gZEŽrf ly/K[ȩဏ~3mx@2KGEO~"/F={:RRXR"ޒHFk5/ !Rx_~4bH.V;s'fYܙu4cAt;3Px=9zMᕮz_v9|5@/sY,%|RdIENDB`twinkle-1.10.1/src/gui/images/qt-logo.png000066400000000000000000000102441277565361200201760ustar00rootroot00000000000000PNG  IHDR>akIDATx{pT}ǿ޻]o EO,{ 89rpgeJxz4K7/tgWj)aހ'$S1J"رPh*M;pF]ehckD~ocA8' (~ųi861kyy+K7C?Hx.]wwzpCElo(w6fJ <{]ٜ<™ww("-U >1wcsYg=NJelUȠiiPP|0B{+'N@\XSտ0u4irY!q\U'04Y&oO=qfzoTKs#G;cRؑrÙpF|WP[U ÔP4?{V@ّ#Ҕ!X^RJ_@! ʍO2L -z # |H}o%R. @G 4gݔsgH"0jM) zƢ1)Tj?=K6-L=/pp.2zS^JikŽJ@rFe%q9l7My%Z^lhG*|oLȊ1=< SnMsእW?P̹7bBxJjSK6wIl2..Po59n9CW򴶧4LEpqT ̹ZkYIID |uЍpL"n BP$]Q5r xfr٪sJ5X/d 9UXH@(!:Umcݓ) SIze ܈R42s,|k4_0o8^Q<@AsdբRP'8+ N$}>?cGA:hbRuƭVbAa1vr\Q)ze)TF}94 1yYIU?pQ@k@\B`TJ̚=BeǔLZbmNQecRHg&aSj-`̼5vs 0hʸ:! 5hh\6bi{ŌM 7zHbRPGŹL`t$g(J(T1z9jlA/^RqaT;fަ{+SH(( P7q89.E0 3ϩUO*J0 OPEpA2J2|?^Vޡڝ|l0@x=jR6S2mz}c NSZw(bzgtx]Mݔ'!рڕ [mh(@ACnS*>ǚ66GP RuJB65i *TFs!#l L Phzw?A@8ko}@c3PRjTS`cݓ=R`[-b <5yfc(u4:+ rTӝR{rkUH 7pFa{`sPsٟϭ׽yWQ\&GZVM#\Kjjs]^,M_o|MpCh8 ,UIL5/_/}i:7QT0 /i'!_d }k\?۟],:#DiUphv=\p`t[y=eŲ:jP; )"6ͩ[M#L09Be"S)<8ުa@  C=³1%s%=nJ=׌'lF?뛀,P]wS8ssZ|@0fП8 @\0st8݁LzIf3pg>N`jJzQȳ uT*/O蟯-qIENDB`twinkle-1.10.1/src/gui/images/redial-disabled.png000066400000000000000000000006011277565361200216150ustar00rootroot00000000000000PNG  IHDRVΎWHIDATx=0FW@#mt\ge*d[&iSy71' |>9MRdmNn\.f"@UͰ%mNT"B]׈Mp:G~Z뺞̋HV3JQUe>zιwm0 Ex=kTF 2 fpIxapIUcn.EzO"2 ó}߿9@V9g+.T={XpΙ\Qudj7MAR^chiǿc%ԕP× IENDB`twinkle-1.10.1/src/gui/images/redial.png000066400000000000000000000006511277565361200200550ustar00rootroot00000000000000PNG  IHDRVΎWpIDATxJ@ϔJ!V RhA7P&KJ$OH^M@n%IWLbтq&? sgjzNBUZ'R9^ko z>6+XMTZԄEMLf# hS{q+ lT tq9to>9^jך>L x8dJI2tjǀҤac gg<#UõX߶֩* ?n.,jj"M8ٮhޜiV*E'#Vų7-H8dCllFGD&) [ ?"T9%ڽnufT;$O :]۞ |[IENDB`twinkle-1.10.1/src/gui/images/redirect-disabled.png000066400000000000000000000005061277565361200221620ustar00rootroot00000000000000PNG  IHDRFbKGD̿ pHYs  d_tIME5*xOIDATxѭRPex\]by4K`TfRӗ@[Twt:DnhQ{YP2L@YG)C6X@V"@*aƃjic8~6ni;i룼s_xNP*;)4/ e(bÓ2q{ީAmߟe!qYEgpqT^/2c;7RIENDB`twinkle-1.10.1/src/gui/images/redirect.png000066400000000000000000000006031277565361200204130ustar00rootroot00000000000000PNG  IHDRVΎWJIDATxn0T{G+L{L#x WJZ2*~J76ꪝtRrLDS\m6TNM-0 @UYJ 0F@G6 @#,Nq;ݫ?suޣ,{`:`'"&rhuSfl6FiCս4[XfG]{ƏϏH}'eQ*<m{TT}H{F "VJDB"m{|N$̄QD]/]`PԩZҭeFDwѱO[K:{hq5rm[IENDB`twinkle-1.10.1/src/gui/images/redo000066400000000000000000000003141277565361200167570ustar00rootroot00000000000000PNG  IHDRĴl;IDATx= FTc±H^ '"`-$>@Dj! Z lLd@ >Zc8.CiRZ\i?$3Ձ#EfWub[wo:IB{,f5sex]zIKvМӫkK 5*"`|X$}#hA7x24и+m|*rͭkI SDpH$MoML"0&ZAQܠ?E,Z #ћi ^{vq'r %[_!+GzRI( ͱ= S}YOٶjrѷ^p8n&E`ـ` oiz[Dw-{:bQNEZ7&(" (4UJ2G>iJ1> lx&RDŞ 3=Pdhv8#NMNLUAxl ns$pC3ōL%Oy|'x2Uß̲(I(gD89|xZ꩙C=:95ϥhvݵMjx>3JV|7N+';HڋgocvlVV%<ҨD)83IENDB`twinkle-1.10.1/src/gui/images/reg_failed-disabled.png000066400000000000000000000011071277565361200224400ustar00rootroot00000000000000PNG  IHDR7bKGD̿ pHYs  tIME7YIDAT(MOHSq?inlҶCk5 ҙoѠP:$R/]K@!TTB-0HRn-ۿ^ysܾ$k@vVMV4z|bp黤߄CLj[ 늰qUs56U+Q~;`ys6Lqc^ DVbRC3M@L%wZ0E=_yD88޶Q ig 7_Ku^OL4=w~5"i2^wQRzl5߶Vɻ/n/]'|Bq/·m2-*Ǜ9VYDE_鼯HYًiŅG8Vo,!{P-*G.$N$fG s}'YfMJ&7EQ{`j+6hIENDB`twinkle-1.10.1/src/gui/images/reg_failed.png000066400000000000000000000016661277565361200207050ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME6$ kCIDAT8uQ]Le=~]vc]ىAen"qٍ7zﭢ`41ju^,lp SDC-f4J+ ]yΓ'`כ.9>ڡ]v_ezJR8tLz!Լ`M+?b#y~DqHWA/Wto𴆡^2caL-)q;j3}Ph]R]ҧ/b$d81Y:!p\N"*xh˓)WR_ TzG RbB8>sTK~!"5=0fiQ~c5ǩTnb/h6!{12tmL#ӌPnC$3&hW>AU&֓ Pq=L_$(Fqgqk4{ pzr_laZ$02Y$2&i4~Jߥs c3:eb@UގՊFU \{ݰV9MBƲ`mc@QL׼(?px ~~Ӻp4:#MQ @)(͚NF iW]r25֗ڮ v`u,Ua434U筟Sg nmaR&>-;՗ N0^)EIENDB`twinkle-1.10.1/src/gui/images/reject-disabled.png000066400000000000000000000005511277565361200216350ustar00rootroot00000000000000PNG  IHDRFbKGD̿ pHYs  d_tIME6 h<IDATx-NQ5n`U3GB2I7PPưY XRA`7?U{y`ab^jw㌨'R*&Z6P2͈I LZ@3N i7*j?j 5~ݑ65>!,Զ9u^8DVmiU$LuBXU0as6mnG 19 pRw?ڜY4M3\Di}`4 (lWaF,QQ{q2 \lT:=X`"X~cV@my ҥe)(O5RbjNeN 4m)g^_8RR(IENDB`twinkle-1.10.1/src/gui/images/save_as.png000066400000000000000000000015061277565361200202360ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?% X@ĒGqr }? _~g ×/~w Ow0\x @.XO?iˎɞG`>:z̟?oN+{ _a셯_1|:?>6V~c߿b7?116 s QTŘ!߽RĘ=*rǎ5C!Z @C!;++#3#';!^.W@O?  @; 4Xr`G(a= .]@@^ tf`|i ' Y_?>'v?A O80‚ hׯ ?;Û7؁3 LL@W`@~@Ac0?Pd 4ϟpb/^axSEׯO@ Z`F_??#Çׯ} @_7778l (Iׯ3 `2  IENDB`twinkle-1.10.1/src/gui/images/searchfind000066400000000000000000000012221277565361200201330ustar00rootroot00000000000000PNG  IHDRĴl;YIDATx!9Fz,(8lh]kfÅ=5LblWm.r!KIn-#!TI)eN)sD1ciss>c` N^Ϥl6]E痗6;6_vjaTߎH6?ܟ{ϴ3Òi cBɅvA xH)cduH^RRfIFoCCm} >Y]PXWMX ~fx=Ór|B'f@1߻wL$ c;Û?T~sug :|~E7 ߿ygnz,s]={cdT{A9~FkKI]| bDb:2[$t_B\ rkyuٷ0MČdO ~ 9}jfFJ^/~ %Č-`^}蕟988Jfǩ!uSy@̸w]~QKM %>3(+p>ˡtČ/rEYV/Mៜ { {~ @̄8?.F6:~;M@$teTy>  @D520R?Cc@G mIENDB`twinkle-1.10.1/src/gui/images/stat_conference.png000066400000000000000000000015241277565361200217570ustar00rootroot00000000000000PNG  IHDRaIDAT8_h[u?_4MSӴ5fivqne:-sVн A_huhZuk.%Mz&7ɽC "<||9H<c#G6Fự3N}}`A\<}L+Iq%sUEhY@Z<\v iT@.sSmcQɱ@{نK8_~_K JPM&LbWވF' k'iFE"_Y+_|)nnݚ5-,AYsZ!d @ !mX~ (H}C{u5>Mk*z@}!L3LзZg?9w 7?5ر󹧞tDT"ynFYYgY͐,Pts?/PJe QΩ$SK@qeBN9bP»?ur)Z6nAX4L8~( ޠ;,o^E9lqjih.N-.do8q{֬q$Z_ (|tK²0<e`+~=BJj959sYy0a#OX!RFܻ+rlng~p$Lm@u(Е\ǪUlqJ\#_ss*MuljKP*T~$(…*W,1׵CX`PҔ=o7PCɰl='TDA\\1E)(27ojR`/׺?lڻ \ u03s9UnF/ uW&w}HPW o|S ܐdɉOY[<4>A+VD4/YIENDB`twinkle-1.10.1/src/gui/images/stat_established_nomedia.png000066400000000000000000000010231277565361200236250ustar00rootroot00000000000000PNG  IHDR7bKGD̿ pHYs  tIME & NIDAT(UKQ{ofvuk~DA V"<+ F $!(^ $"Z;X$DTE uY$(Hܭٝ?| NI}H!aAzKQ4;}l13ZضƓvB*?z-w{ K\F1@C av\!A"(y`DYwNDpܠߌrI<.': jيGbd}s+L|BP1>G DD= [-_Bb Bp,Շý( 2oE #SlB,1D}_mf)nYnZ . w_~^O ,EdS(mDX#IENDB`twinkle-1.10.1/src/gui/images/stat_mute.png000066400000000000000000000012701277565361200206200ustar00rootroot00000000000000PNG  IHDRaIDAT8}KSaƟ=̳um.2im!B *"8AwM@t!/j b`NSqe⏆M:ۙ<%8 "`uöbMkgԴҧzIMPpuy}xwDXFHgӏJf 1`S>\<FƧ:vͲ7>:^$%:G:] feRErz;/]÷qC`sǽKFgj X]b#g5}YՇdY~H3"81{JyjNΪޥToPު_c]1> >KED mZi⥩&1bg$904?,X8Ư1?).ߑxmBRkGS*i^H4;͆960.yĶS.1&ȲvTр'nQ4un%k09'ݲ,3sqW3oE==-o55> g>_]-=ܸRa- M4.x4l47geySd "1B*. d«mfj~$K\2!ggrv>Xo|5 ;}٧etA0J28*>1dnq٩ ]ɚ֏.Y.<ZCVQ `-@} 2!c? 'mʳw- ^ N`:h,,K㲌o5A7 .@=w]U9px hi|qko|irKRQf$bI6s4ʠEK 6}YU%vcpɲdE■ES0,OVv.7ˢnd=hy2B`.;#O@"PS p,U;3wǁd8D7Åe[酋AXj'` K7!qyL 8KAssA8r{rcHc3~M[Eo[G[̳LKΨT TG!:z +'Ȁ1tL@$ DA$ (p>qShܾJuzTIYQ4$s"ѓ=MA[O5BϚ0t naWo{n}uᢵ2pΜ1o;cΔ'rLbwfE' C7p񲍾Ӑ͈iqhc_b]co0컠'Ko6s,:I#Ggv N\ݰS_zKLۃ1~K͈^l}} Ҏa.cITNz5@B,0L9ZWTG]7y/j (5 *{@CI)]ȋe Pnlb'$ٴiiZcWRe,1ؙA>ҫ30h;4ӥc~^<ҩN={'?; %8yc~3(a:e샷]-$c"Xi2b:\cWKLD9TKMEB,/c2!pTpJsEK?ROWKo>VM /ْrݨܡĶT^H(fuΉ׭v4J[/q4rUyƒͪ@ UIENDB`twinkle-1.10.1/src/gui/images/sys_auto_ans_dis.png000066400000000000000000000015231277565361200221620ustar00rootroot00000000000000PNG  IHDRJ~sbKGD̿ pHYs  tIME IDAT8uKlSG; $&ܐ# $ D *l˰.  ǢRE7E `8UIHlǽwQ 6333?gsB-6X9ne?:{Cs'J55wP,dvRlE~Z7F, {cVtڵ/xI:k:a$ PUڎWd} 6 4>`RoN!/ BaV :vSZ+k.^E~Q"q.؍Z^,ufZwidmYJi-g?sE# ެ#r$όgx$f޹,$WD/$/IS٧N>Xh4Lbc2ẅLGDV|* ~f uDH~xlfl#^)&!NmXTˆ[$tphbALfX,&yjhF2Sejbz<i‰]oUذ{& +F"H֯'Uht ӗL$.-F{[@ Is1qԖ-z:#1Q?wC᳹a?kpq~ht;.ۺ+3G5N M_ݪ FA=X,z X4IENDB`twinkle-1.10.1/src/gui/images/sys_busy_estab.png000066400000000000000000000026701277565361200216560ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME %AEIDATHǝmLyyC)/cADR7hƺ IT:ae~1*[8us}h,}QhVWR9sC4]u_r]eav*'FZ|ҝ./H$qPz:AXe Y@w}o[!XV($8ɽ\\o>ѷ2OyI* ֭n⹧£O%έ%T"qveN2*vؑq]W<Yq]Ag־p{fI?ߦw8#.xG8f` TgR:G=K8(r]aaJG"@j^o?Q)R8әT@sy>S{Hkm(Bee%@!$Ҹ]E$Zs*jʠj4a;֐rw7ښ<@SN133CGG 8p!=B?uQ1K⁍&t_te|2?=. -kx~P Yxg D"Ǚ,ژD4dyǙ%\ߎg~b._BUW⫋$YVe 22a0Ӕ8mi:9/#TɩIL zݐJ)468~Xv§e6X9lQ$.mJ?§/ɝ\68Cyslb]GWzij*8tvv )//χm\ +[X2=Kt.~55~v1xC}}=---lR8`4Vo',{9greE7b^K3oU[ cXMri6>Ce7hf/s,WU]3͋f)^r- 7$$94ޢ^5ۦ᠈/WXxvYu0O/h3_/?V"?9jһa&6vu۔V]bW|gqa6$"jq`_A,W\(3D4$xYru]y~ߧ?\Mm̮ktM==# ]!]]d}޵On55rY+55Ɲ2ʛ\IENDB`twinkle-1.10.1/src/gui/images/sys_busy_estab_dis.png000066400000000000000000000015351277565361200225140ustar00rootroot00000000000000PNG  IHDRJ~sbKGD̿ pHYs  tIME $uIDAT8uYHTaޙ;3qt2T,Ҳ3!"l ( |hڃ( *B"Jˬš1s4qəzt:OssfRjv#!bS.=9ds](N_zUʼfoۮ՘W5;+ I2q`=f$PbyK4Hln"ˊmLɫ.:E\SvZž4nŖMNk<}d~zG1i´RKԄ=+2)/k~4Zx렛 Z`HyΝ,圤3]C@?61Es Q#|PA5!'fI h6E;.ـ( XIԓH.tK7i*\8&NEC2RGnjFo|{BA_<:-+QVxxi4h>z}߇"3v_ٻ:y |h"x4"xR\{) tޱF=HԈbH 4"U҅yoԅ0f0BhQGS-dMHU\eXiN706f3z!Qoݴ ċZ3v~_Z0)#rѻ{)Q -:G2kjO1^q!1K˿SEbZ9ݺczA|?I)ڄewb51ƇXW IENDB`twinkle-1.10.1/src/gui/images/sys_busy_trans.png000066400000000000000000000024721277565361200217070ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME  /gIDATHǵ{lSUǿmmF;mg6un.@,acbFH4G!1"#j0Q(ʦM3 ,3# 0evsk{=BlD=9`! ,D{֬*Y85LǙ֯6m|EXa^'xrk8áY*RboB's_f&6mZ+g`S zҶ6 GO$:=Ѐ(aͲPvoBuEka`bb{XMc((Bn7***`6L& O38ckBq(χ~v466BUUH`0lkk;dͶݙN˭ `[)響" H$I fdՁN~'Ɖq~sv;ò 0ID|Q9ځYł&l68l6CetCO[4 w$4:t8rO/~ކɑ(ڎ@ͭMtz7njC$pM᎞bF5F/:o7W k-G Z \G?FGJ"蝝=?ܹ&gٴ[M$zD1m8OtJ_hv:1-߳5QN K,XXBۜ58u +T@$+&,ez 2r\ V\Sa% p)4L (Vt;M˜/=ġ[ cƂ%OZP,q%R?xF4{яޗ:9cK ?gkш&4-n8V9J  ͡wD*z3K"Ti8\H:LW !wL}S~%L潴ynO\5\ӥOK<'we8Id1#WW ͇>UkM$^8&QgkJR0t|2!M սs!h%GB=%#XE T Nq2#촡fB;bR;ɲ8K%w_Um a-W6v8GcAhp_$raaJp@Q03Eʱg?˞hUA]m R7´d>#aO!XYK4)C%yFE !hgxeg<Ӑl%:4Ză@"/pDHh~^d낍k1)Ei>oQ1!V{\:G+g3|}QhuGlm#%,zB X2ǷV8l]IENDB`twinkle-1.10.1/src/gui/images/sys_dnd.png000066400000000000000000000026671277565361200202710ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME pDIDATHǕklTE9sgnRi m C4Dc"T0H"1ib $!-rB؅-v/gB(m'2|LId[b6Sra ^2@Mg=9LD`vc(5CP6="uNJrI Z[j6Hڥ6OQ|U19׀7cq4 O4ߥtd~i;V7,D{{kڣALڬ]7 Do)yVH" OK5 fe!!A%做L` dv-̺Vo? T$ɜHIa&$-xbz:%3]0[#v!38uj T=@.B)q8p-̄0h )@HXGğ hs >/Ԏ?jk!(ˆ@‘ 9U4`"fG,Fc#fQLeX0eoiS\U#q<~pnخ =3ϸf t@#qx~Au@-@i ؎? /t_/bZO{&oaD% BpTG(xS-,z aJPK[Az1l`<{1wXkؠ?}Dt߿pF82' afܸh{^M:"みׯp4ZZ~mR!CA6B˝Q'K (!֎SJrZOB͞HYe9d2HRu.܌pHQAOΞ׼w:iO_P<ܻdTf45&+yIR3bν |R`Pph07}f`rEh¼hQ]S8bf XSU2((hAH ^ƦMgi4r.JH`57 tCO0$BTN%4i~;iF<~=8@AC` 7dr1 !z]9"W`ᡏ>111 G@اvO7ר; ~F0袳Gۛ" ,:{R f0(^2aDFS˦kk}Wg :ڬ-%)qJ.yb"<ڑ`T"P^8\fG )$:HE.~bJDCsMKA/TVmI3RٺN+4F9KeF[7e$@ɜ2}ͭIij w(f2gN(tIENDB`twinkle-1.10.1/src/gui/images/sys_encrypted.png000066400000000000000000000025441277565361200215130ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME.wxtIDATHǕihWyKK4K1MM3iꂚ/Ui6K#AH.`Db]@,V۪Fъ6ҒcԤѨI}Y2bL2p8=s ]tlYU}uuiiox"Ex8h떖W9L iE؆˫yyJENNF pCq…,: \RR݋wEnWEf ycz/^vuGZTTׯO;:pė~|٣$d,B!C_+[tYZd(T3⺗A<.GE-cf+=@lɒ ;d#kٖt{ͳ+] 1q { 8Ӏ(aCrd[^ʊr;P4M1 a޿RčkHbf&L'L@ P{;ƙWu]鷈dq]ƒBQ W  3,sҖFз!]f05 n'`X Yxܠ׬/K~kqMӐ P3Pn%Ck8e$sKxnڻUF$vB0lotKe~I3<]3]ipR%XzPhE_ݴ3ZA PmHMg`Y\H/>M((Z(ʃL>2EiqE>jqw>,ɂ(WMk6o_檫b{gvWm/+_;gڻY]U[_= d!IENDB`twinkle-1.10.1/src/gui/images/sys_encrypted_verified.png000066400000000000000000000026771277565361200233770ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME  ˪_LIDATHǕiLTWo޼fAđZU-*/UcS[B i%m"FMiZVM>D`"BQDaXfy̛y!F &77s~sϽbbv=]ZlKll{6_u=ߌ~lhx3e(z[pQBZZ^t焢 ߿OxyyȭJ]EF :ÞokkT5Q8֡AY'q:x!aW?ۦ4GԻ~9 idY T 00TT3˶畋 Gs)ݩI\æu)FH^ ʜ!0*>q.GQ7(c5+ ~]\*f: zA!P2! )(VX>I1pՆ@4&dt| ~}w\}QWPbE@c X(A! t8i`y+F`d @_"5Bn…^uiQaQ7nHnXQt𺎍D t4cҩ!#>iiże ڎs#A&wB0Xhsj fxlcvS1]*g1@QwGxi!M()`bqvlJTeE 5B&eg8wgˎ LaSwc* c Ԋ%Lլ_.Q`>h$JdiK">v}\upޭߗI~'<7+ $(JyKvm*?abvϩתּ4H&&@j"90U_"UZUN86kNBD'Z~`r0khDe\՛Wf堷3x!/?m\v}SIENDB`twinkle-1.10.1/src/gui/images/sys_encrypted_verified_dis.png000066400000000000000000000015101277565361200242170ustar00rootroot00000000000000PNG  IHDRJ~sbKGD̿ pHYs  tIME |;IDAT8u]hSg+'Igb֨e&6jXc׭E":7b6*Eo)N"E^Qqv**jl]v&6'9䜼h</<(tM>:fi<jAWͫg+=@}zi%Hz~6tmp!"A/_wX1?8c*.:S?;$whoOTj9&Ɍeacj)q^R QQ ,-bZ* *FfrK>aPz /_,cCj@c/U5Ѓ > 2Cuͫ>E #:'r<9YGB`r59z$ZDޖ搌 ͯmqAšLla@;gŰ0%rEkSi]|˅Ъ__v$B F>T!s[^'ZIENDB`twinkle-1.10.1/src/gui/images/sys_hold.png000066400000000000000000000023371277565361200204440ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME ( dlIDATHǵ[lTU}t:78\[%#{ 7E"(F$(*$51D^K Xijh"{ Zl*m-鴥휹Ϝ9gPېZ蟬ogAسf)sˣk8F4/O|䡴 &#$jEѶ%k<[hKtv6{< D+'NVogW:\ljƄ>N@IFW;<zmZX"s P}xȉeW%U./ϴKv' | K\n}  `qDbd#s*.Ǜ((/. 02޹GDydAD0=Pe /5+ĺg<&ØO1Em^hƖQ$ϵ pBdN"Aߩ1޼m06 | PFu'\h!3ŸÌ1!3pPKk W~W3n$ bPKU]<%kFr'CC?F`q|"ϋLB6;Pl3l!vN0FWy~}q^Q9!ԯBY׭ ںA9mW٬,jqGFOٓKv·}՝rv+<+ˢk?YʧԪI$hٽLe))iberU͑jI6UR]?0cZV֎_'eݙyPjIENDB`twinkle-1.10.1/src/gui/images/sys_hold_dis.png000066400000000000000000000013111277565361200212720ustar00rootroot00000000000000PNG  IHDRJ~sbKGD̿ pHYs  tIME `ZIDAT8ˍ]HSamgiDV5D4% dDڭQeRE}UD]tSTT-3$ܴt;{o^y?BwfQmMZ{j$,Ǒ }QL^2$af',aXzW?Lb"WzA9 1M8PʚqO18bònKq gjm>ru?bE'*QI̮i N` &e+!J(Ac`P^  Lp2XtgR+]vjePnM{/%]|dvf4gC'J-_: nu*1oFʱO&7[$j 駋 !L} ?94 pE\|(d?Vn$6<đd[^3?~!ۙcAb*W^^RBXմQGA f\׺ePHc'Bީ;wVRf_nsn9kռx҆$<۝Y~i5gQgI=޶ 黲)'ԍIENDB`twinkle-1.10.1/src/gui/images/sys_idle.png000066400000000000000000000023461277565361200204330ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME ;%wsIDATHǵklTEs>fn,>h[ZRKRBm$ #Ƥ1Q1HTD+|#`0 !ACJK n>v_ vI8?;sΜEQK@eVrdN+32 ytAAW))ZP)YP9W $PvjDsɨey[-ʁ h 륀"Vv[.E@ B lrTGBB<$ydLae%IScE=-T+Tz%˖ɨT$b]"mf!pY3)cFiF,34$ܼRD˶b+Z[sUIu; N01:=H 2_5(X `҈L8;w?9jOUvU( PB7᫈'Q!OfoCqP&" &ƮGN ۗ(>?x^/Q4"עx3er` `ZBc P>NS qx7Z]I]>m8K$J9bߡ ["6 RIW!ػ3ru.k>+|*B7cDZ !G1."ǁ8!{߅G'xG2ꃉHjsJ<)縱W 32q ыQT.؂ʆ 005.ML pW UP8}z# N2h4ƲZݓKaY8TXl&&@0h(2Ӵ2}*BtH&a'pw8yFt<۾ B4,kPmxr~<njb3Bv6PrrɆ53xǢe EQ2Obћ:K|zH?$nk'Zy8\v~X}N)v85zř,~?;mY$Q hKv<N8;ܹqhVI,6*⹓GU}#*v#IENDB`twinkle-1.10.1/src/gui/images/sys_idle_dis.png000066400000000000000000000013221277565361200212630ustar00rootroot00000000000000PNG  IHDRJ~sbKGD̿ pHYs  tIME % e;cIDAT8ˍKHQcy8>t|+(I "hVJE)\* k- ZH"" (#(f:m|9{Ϲùs]3Z/[ۡTgqc>`(>W@G Pu OVHX9j|Q)sk1J){Q.\U%RO_`'9LXW#!"ݒж 6M: l@)l-l"#X=~5i p2R2 x4oS>|HdAqZ dDBwq0rdjVrhd zQ6Sh3ePѵ1*Ð3;b:0!W^j3 \4jH$]=*0\m]I-n]W1ZIENDB`twinkle-1.10.1/src/gui/images/sys_missed.png000066400000000000000000000027011277565361200207750ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME:.GaNIDATHǕYlTU瞻͝鴥t #B DHxD*QA (aE athv;{d8N}$) ,tiuC=lSfy*pp=1ià̙u䟿]yp{@!aYoLz* v7d6]77!=z<򺐰Ǎ2ZlM5a8el35"NK3ڼ<} Fp e0PQ!;x)}nv"稆M!0y{8~' *׾"N{ddUq, 5`RZ@EiKBR'=F$T* ֗Ai(tǰexdT&$۲Sqb B$L"h |Y,|~vU5YU$1 B&?~E  ]!cʔrOI"֬4YţVQS2M,يP2CˏV q- 8t!@x$Ŋ")/Ld5~ m^7peyJEM"(V` x5=bJ*xp:_X&VnAеj?ƜvQ |O'A p:(}*[ACA!zf#= ,k2(t=x3` X@8 ~|E7&"\Ldcb F[}F'`( ʇڄ_.E5WOp(CՏ}\n,g{/Kӂe,,buK-$I̜ vؙ{7قϊL/ޱѢ|vJɱy]R*f: ,˥+c|͋} O+/z@8/Uo8hurr"2qf>W`ّ͕D)E~}mr#Ozr9vRIENDB`twinkle-1.10.1/src/gui/images/sys_missed_dis.png000066400000000000000000000014501277565361200216340ustar00rootroot00000000000000PNG  IHDRJ~sbKGD̿ pHYs  tIME;vxDIDAT8mKHQ8#3S*]A0TXhP+[YDEتZHAePAih)I:y͙9-B4{bO<$9u)|%ɖa$oMS'/kd )*mxwSWn!Ø9lce!`dXBQI"A`ih[u+$ c;F ^^/Ppq!+k&@qu* \mfK3y2 (+[)SvW C@fqWrD2ğq=ɏHAL&\[]IDD2ᰉ<j=i-j+o0ƛ;84kl]-L @@Bͭh !%bEv 7:THO[PXFKۜ0 aJ`{]87Z^v;$|cQnV9Ѧcnb0 $+=CEW}8|Vȓ;hإRm̰|:{o;Cͩxh/ 8pu8+f 8F&b {his̶کfϧ;C/D[OƏҊ2vZ''1x q6tB>g?py<͏3H'MC:Lz!=wl6]֬7 ._ ]4_L6'D9ʼn9neXW s N;޲AB(6XXƭfa1PHv۱z5IR DaDD0q'2:L Eohgw#0~# DGdTcW}4J")t8V@c3i;~)f;gm=jnt8;g,eѐJuv; ,$CKK?؀J#m9Z E,}zfDS_ȱ+-Mλy?2N]X73'{oڽElݞIBD*}H:wCEf`z+T8[dyz,7"+IENDB`twinkle-1.10.1/src/gui/images/sys_mute_dis.png000066400000000000000000000014031277565361200213200ustar00rootroot00000000000000PNG  IHDRJ~sbKGD̿ pHYs  tIME /IDAT8mKHQg~gƱiFļe*(p!""blUBP lU # !ftԄT2SMmgtqtP|{|H6ۓ< k.S߭R(zMzVB::ô3>z1L At-g6; vly9L0^fI py  LN>=u1 7S)opn%yH+H8b^)Zcmug@wu?+)]n=Ѫwj*}R#eηRXvd|d,bۢV\ $sH<p[ yw!y&k73lyo23g + 22?N~v `,oDPu+JIL5R$ȱzĂy o@aHټ. pQ"(@4 +jYsgUZt.(>PFEQTPVQSCB,K@^o]H=ѫ4诜Q G*!:׏0ak  TNr)rtwkP_W,_l٢{60^ E pe52G@kk!q~)22nyM;[ʞ<:܌<W`в*'_hepv{S=8{:ce" % v Kc^Y6 LX:&#;_}|]U=?9:M=>hC Hz*9ůqcqof39a#g򓬢VE (\8)YQP ǣ" E> Cc#O3@Y*B ~P`1;Pd a `TO*[4O6wC6{z%VvB|TPQ,Nwch8nS^|/ Z(.g}m wZ3#F'ԛN|ms?%Ҭ !A7rǛhԹ6o^S'O hX^mzgnk2Y%ѕkO~eVўP@˳|nj='D 7[>7 VaIENDB`twinkle-1.10.1/src/gui/images/sys_mwi_dis.png000066400000000000000000000026271277565361200211530ustar00rootroot00000000000000PNG  IHDRw=^IDATH]H[g眜Ĩ &&\~DejZ(R,,n0ؠ2Eҁ7na+ڒ952RmxNLؕ"Ng\><}xWeu%%%_$T|W}>ߵ9UK.}g4:n<6vrh4ڳg~~ǃ磶2o+.r@4qp:$AQh޽.--AQzXVlnnɓ'E(--ʼn'PXX$Q644tricEf3JNw<$I$N> ( $I(wYŖΓ'OZ-@e $!2|>X6EL\,Cer9$ $IFxblzzz2Ͷ;|a@$4 ƐJ@QX f!b~~333D" bY/@M&KaGYY( ֭[MMMMӈbD"ew|>xfgg8C7#*0MC0 N:.3 x p\0 }}}F\__ 9H~_YYq֖ZVbhhv<ϣyyyXZZF]]z{{qƙx<>#NC J-q]]CCC0L xHRKr;?l***F(477`(`YAfwΡ3EEE$ A >a$*jjjl IudsCUUd@|cc$\.$dq_|sdo^~kVMɝ?`gYne7:::~Bj$Qs rgۃԅ/c6GSEL,..7o>,0ϟVz?zv{mml6{ @D(kmcc#/ q0 c8r=a2VURfڵIENDB`twinkle-1.10.1/src/gui/images/sys_redir.png000066400000000000000000000027461277565361200206270ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME 8F{{Kp, =y>';CܛZ:↊Xdtt:]\_ L8j۽ȱsl#}+I>>xeٛCqZmVT7]LfYiFxRH D1u-/-T7QЁ1cːpB3pIhob"}O.}N", ~׶_< |a8 IEYf Jv@T7r]/pO6&3H%kשSF}WXލۭlٷn". i0hhfaWXxbյ̉gꌭw2{Zhͱx5>pP G ʅ|KO@`GSK jj/4.m&~< ş B > (nGuü@?Oh~ư ʂؒ:ǎ@ KϯG^Q!FzFi7*%iFA!#- L  Da$0qla]j&yI3&Ja%ZQ}QV#j! J+(ώYbG;h/ftR>72+_vM1"Ub%*'DQ*槪Iե]D2I>۾IQ~ ?&zN,˯^l;/۔ 7 ЅZ|GG># ruyYpK}!" sqN`x)/& b #yYaDUe/IENDB`twinkle-1.10.1/src/gui/images/sys_redir_dis.png000066400000000000000000000015411277565361200214560ustar00rootroot00000000000000PNG  IHDRJ~sbKGD̿ pHYs  tIME %:wAIDAT8mKhY?ici6mjTv%((h]). Dg1 HθpaBBMzSԶDkm$&DžS#c픃E#V_,Od֓@aߢ钕D?]{:# s%/Ŕh㠙h)Čdos>U(̋|zY?,F:>+2l"eI䏥<0;TYm+:6A \/1y!pV{1SBRC T|Ɖ{FBUj\iA pw=C)$#{Sg9\MwYv,ԑ$B"t UxBkp w(a75lCl(Cە'5dT$tbYEYL:RWzV 03b ł]{&?8ϩ1BNlXI3L b{}5t 1ÿpH MQm~l [{IBH=3 HT<4D ##.S{r X$_}?FL- 3DJ[2 …#-' & V-K^s[ b57T=^q>Q2'KX=Egj-oŲ,R>Tݷ8llZ99-mGC~[ IENDB`twinkle-1.10.1/src/gui/images/sys_services.png000066400000000000000000000025021277565361200213330ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME -}P<IDATHǕU{LUu9s+p +9PBF8Lm7tւUͦΦX,.(c,e35 q=5qn߾Hdɖ9^LG+'oX"uA2 ѨhTW cwx ALjxv:颛}= mz\U3,|X|"Kp>`Q5vU\%ܲ&>` C1bRG5>}0d }ċ<)fO&g0>~oaPel+8MmWFak@_ms aZ㙈N?ZKf+zzϵt\Xٷ}bhՙOOI3M)JU-4~,Q6\n$J'g4w?`$WIENDB`twinkle-1.10.1/src/gui/images/sys_services_dis.png000066400000000000000000000013701277565361200221740ustar00rootroot00000000000000PNG  IHDRJ~sbKGD̿ pHYs  tIME &xIDAT8uKHq?>Ps+jCkDLB,(C$<E#z"*:D RY(=4H!ٰ+QF&殻wCi>2 35;jet{ @m.G7; b|ۯ?]G~UK5,iC,gܔv@-F. ɩlt ĬL}ӫ7,V!2#%dl^F>V͈wۃy/44T446jN18sf׾}$_uRXԭ"a8A%>MuT Q{.| $r57Y3U.ߥk4 b!|Z?Yɣ"8Uy~=$ sv[> jtfQ;T鑀\f)׌rgQ3wﳧNT70Gu)k.4N~s/ZU9jswFIENDB`twinkle-1.10.1/src/gui/images/telephone-hook.png000066400000000000000000000011501277565361200215310ustar00rootroot00000000000000PNG  IHDR7/IDATxc?͢q,Ȝo^qš gubٹ7㻏Z9ފxpF4777=8ө7?\}Ta&<x "?$,?g}/LFRb@$ /)+aKqT߽hosYYcU!aVc(+~000HPUS%u$%ORSBa'''f+au($߿8<ͧX{pRĥ46.wbdQclufӊ:LnDq %޺ G}u ,קe YP޳/~&ӧOJDD<>מ1w=nݺEg!@RR<7^1xY]y{^Lx ϟ_=R޳})N.nyU7;{bc``߽{?lC8Rzlɔ׶[IENDB`twinkle-1.10.1/src/gui/images/transfer-disabled.png000066400000000000000000000005151277565361200222050ustar00rootroot00000000000000PNG  IHDRFbKGD̿ pHYs  d_tIME,ZKIDATx=N@g#:5 (8#H :%(iB&'Ii ml"vvޝof= seb`z0ȕ-F7 Lp-ͤyċ*A&y[՚Dzun6j>΄hKG*n VV~gc-8al8]T$~D&_tN+ -[[=6^LIENDB`twinkle-1.10.1/src/gui/images/transfer.png000066400000000000000000000006501277565361200204400ustar00rootroot00000000000000PNG  IHDRa~ePLTE0/e4/OHpR^`llNJ+uI>pn.12COPGtKBa`Iurm55c)l:7B`Oma}1/.ω̲Ɠ(̴,#2*22Y˪Xbz|Ӯݣ jtRNS@fbKGDH pHYs  ~tIMEkR,csIDATxu 0 D%dr }cZ%fKXI1n2TX Oh1;ʇxҽTZ.K,pu-˘2Sn [tys(<DIENDB`twinkle-1.10.1/src/gui/images/twinkle16-disabled.png000066400000000000000000000007011277565361200222020ustar00rootroot00000000000000PNG  IHDR7bKGD̿ pHYs  tIME6 ]WRIDAT(}+q_~e6qpd8 GBJa9-NJj\6E6s;𾽟<$ɥ"t Kx=-c?+(_"]6y$jXHC ;iT8;%ʬ_kD]ۢ X0b; FybD"O<:>lG(].LȎ8I+ RZE!tEwAeD_&XJbd!HRPDsuvyɋj|{>*A)MVӬuIgwf,_;K+/'DBݚ2IZSkhjFn>_g<,f&D8Ibx`08V\g@RWl6XRr063^P>;ٹ3R'zdyr4] Xe[WB@kxij.:-u\L<{yPG8KN~(+5D$ IENDB`twinkle-1.10.1/src/gui/images/twinkle24.png000066400000000000000000000023461277565361200204430ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME ;%wsIDATHǵklTEs>fn,>h[ZRKRBm$ #Ƥ1Q1HTD+|#`0 !ACJK n>v_ vI8?;sΜEQK@eVrdN+32 ytAAW))ZP)YP9W $PvjDsɨey[-ʁ h 륀"Vv[.E@ B lrTGBB<$ydLae%IScE=-T+Tz%˖ɨT$b]"mf!pY3)cFiF,34$ܼRD˶b+Z[sUIu; N01:=H 2_5(X `҈L8;w?9jOUvU( PB7᫈'Q!OfoCqP&" &ƮGN ۗ(>?x^/Q4"עx3er` `ZBc P>NS qx7Z]I]>m8K$J9bߡ ["6 RIW!ػ3ru.k>+|*B7cDZ !G1."ǁ8!{߅G'xG2ꃉHjsJ<)縱W 32q ыQT.؂ʆ 005.ML pW UP8}z# N2h4ƲZݓKaY8TXl&&@0h(2Ӵ2}*BtH&a'pw8yFt<۾ B4,kPmxr~<njb3Bv6PrrɆ53xǢe EQ2Obћ:K|zH?$nk'Zy8\v~X}N)v85zř,~?;mY$Q hKv<N8;ܹqhVI,6*⹓GU}#*v#IENDB`twinkle-1.10.1/src/gui/images/twinkle32.png000066400000000000000000000035441277565361200204430ustar00rootroot00000000000000PNG  IHDR szz+IDATxil3ڻxG;c q ! -ijBӪRm9T*Q( ")DThH .(zǻ1@?@!Mlיyg3g4i: Ӕb)͇C~}eJEM\!x}~ꍾf}ϼ+"_juLZ jUfu&'nySuFnbs曯L-L7"dar H)Ik DҐl˶@h}K#vk>rV}Kc@(͡Ͱۗ9gVZh6B BpNh d>JvwwIRF`!AXnrZc ->!>wyjY* /L հ)!)bB7@ɽi/0U&&+KK;@IDQ$@!AHϱcޤBN{|S;-ص c6VwͪAByEܿ4:YLY1A{i!d D)u4]!xR垻}wSGiJ/SsWui 60*MGnbM E&34]Ff$y& 9r?oOr3Ʂ¬RHܾ:f9dDAcVRUf1 E?1:QH3]Ѧ^u: \^0'|(З+ $B%S[>o<83@z_<ƇN} zhjd*IEf[D5@ 8z\PˊCA}){N>mѷճQz b'Oj:G,LM;[HPSqφwђ#vN޵R%/ _XgMw ZӇ10yЙq1.j]$q ?7߯U:8ED #D4\>-=l1ɫ#k4\s^Ӈ -vmQ K(L=5_|J"eŀ2jJ6P[ê:=Da1$DE(cX=+kAʺ….ty>0TڳѼRaHM("fqz ð[lTs_#D$O>63yxrknoM^X^BQd!-K.d͋G` ZE2Yc.[d>ZJfBUN{S~< ֮znɁzUw|[sY .]e~t:m k{7뒖 A#ye鉧MϨ)֦ K?:r3sfW˅zuj ~~}dYzE+vmwgNg[bƜ*!. zג_=(N ҩu =53$w%El~ݿS8Eݵ[!0{raҾH~¶$xdRvSWqku4QaIENDB`twinkle-1.10.1/src/gui/images/twinkle48.png000066400000000000000000000066051277565361200204530ustar00rootroot00000000000000PNG  IHDR00W LIDATxy]Wuk3ͳgyC&0$ZJ[6MK QR !1%P gN0c'Nx~o9?%R&K}{ZrzqRD Y@M` rxt-<T*E墹m$A)>4il6/X+^',8cY i7i`0:͗] ^_ki'i޽j1΍[l7vr q9bԆxMQ 6Zv[$I] ! cy㛊\EVƘl!A cb7\Z;m0Ɯ70w'#D@L25I6 c=oc;"aƃ*EĄE熅6 k. 8kPb* J 1*˗]1 (1OH04b[jӦCm֬J~ťΤc+'N0 1ョ` IvKҕ gׇE0JEPZ@1!&y~| "w㨂PB)!XPbLHDp56sVհz[Ƙ,KՎZwwO}nJrgn80x)+qs@(R|e~5?M7=>v׮P|V; 2UV*!uz q! $Lb y<\( tbMBB#zr;9"119hw4g~eV϶(ZЋ sBq.k  PIEtr{r<.",'CGpy_ghNYDL6lX+Cps*ԧ m7]}d":oyks;g,{G&!&b&H`[FEDӜ񅇾`eeΜ;tv)m9CAdn+"ܞaAhhct%7NOO{_22% IJȘ$Tt.%J"BChd|O`kY+W#Ol~o)O\i&zߖLZCaBp"XTY]O=e*b9#XkG#b (1hj yߜ- g>o[d;` SԮEcU ,H 7d2\Gx''[)uW6wqwvݳ?!_\ZZGxb24R֡`! ^$Ci5C#-QwkQP_gXr#Ou:m}cf)ßצݼP2lw͇߃-Nb#&j}G {Yk/sY=d?~|/6sO!s*^nfus;c{ %I&ffMPc\.Pi&f[LS8:-ᮇtɛ]>?ި97y2ʠipO=[wnޒ-[ԟq(ĞK|b xj:6plVwfk\}9I8~4;M|sc#f; ȝS3pIلkT~,V040݀ógv_\R$,>rmiOš?d'^86 B|iZZ! LՔ٪Ky8Db@2^J{_SqikmYhEHcLȂ5>UV>˦mȹdYAUCϲ?bl*1& #(6 )o^ {?kp.C*j  t/v\-yj3m愝`z B&D$BTQ5|&fW%#\r3` ^SIHbB[QB6AL 4PIZn=')y?\,s5ZT /k/+H2S:jP@D1 -)@_/n60AT[$hF#,tw`C2(s+h(Zǘ&W\Qf[X4PbݢeA$B5Wi:wMZM{Ç˻X|u׾V9s}4Aꈔ@Rqrold5d:43 L""俾lӣٵy]?{|`ք:PZLiU$n5OZHh,0a  ΒV[mO:Y~5pću{>kК`綴vx2z9'-3ט?} vT sX$w ~\[nml97ɧg-C}d(9P%1vf?6Yknr'-Y^>{YzuL)"u3*]&MTkH{wMdNֿW237q݅UF}֦7nSrP"{[NBmN/҇6v lXk{XOKҙ{z:l! z㒈UcOڜ#;W%U_l{n>dŜjǓX+,ny[K/-0 m{+:K`7p#k._>Ou4dE +}pR8 C[l֝M.w_߶VS|},Jfv[FC; +bJ//ҚXIENDB`twinkle-1.10.1/src/gui/images/undo000066400000000000000000000003241277565361200167740ustar00rootroot00000000000000PNG  IHDRĴl;IDATx; [2cNhAyILǶ|mFhBRsKRE 10|Tc`)1|?Aʬ1 =6/9e-mZ3^ %[sٰNNNIJO%F7IENDB`twinkle-1.10.1/src/gui/images/yast_PhoneTTOffhook.png000066400000000000000000000054051277565361200225140ustar00rootroot00000000000000PNG  IHDR Ǎ IDATx[lWz x%EQKdɑ-kcٖWq];nSH>mڢEآڢzhnI8eŖ%u"CrfȹO@9~,n4M8@<1~O(~p6Farmxo1Dp&m؍FHD ^,'Et]='OA|js~]]7CW|<<`c*X"~MUNfշ[YƯ< g~Jpv45˻a_nBZZZ aYATBwsm{aWU{XAF 3TBG!?{ٻ' 3ﯘa&3 cy%A/wÁ>3؊hvP(׋Wxew7LҖSvP;E($ @vpD̤ؕ!YnOc-A =LOB &=ӨZ;ۛ( $ $i僾1v-.bhh"eitt{3_\C1c04Hk%PĨ(͵Zۥ&EEc[3Ht{V*ڔM&{9.>)WYw;;:, $t:a!* DQaH$ZSgzXmk-"x CԟW J[3kJSԩşX޷G~ ׿AWU,t: 00;; $b\.C45=Jqn^ip(pT*J$(s>ɻq޼T92X=groX~ZĨpEESSSs΁8|@: x=oGz>݃khn-6󸾇CeQ쁲TċT-u|e;Ҍihll똘8:;;*XE.0e!m /=YY k1n12# ܝUpT.~IAU~IOPEQy&4MC__TUfX\\|V=! ь}=iEY)Ϊ*|>BW㧂yR.[6WF ےm8<<+׋r 㰶y\.sOe]rq춴 瀭^r9K亃v7^pusxh6kxhcZE^ 8::,ױclmmauu@~PPT2h➭\)/dYM&\.c;Zɔ]B&[of܎Ϲo^r%}ppA<8qM,LMMahh,ò,,..>+^bɣBY(b)ڒ^%,Nk뎑NpҢ^7v{:ޞ*UxG(r3 硪*jnݺYx IL&cI/@[%.Emr]{#l= @ԍs1՚(e0;@њ:ӆmTWH }6d)vy\:w dy@.nUE΁JR⸷{RW%BQRH4$!&J ʱjWت&7ʡۢ+?Իv[s[32k0L, 7[z@σC --4w 5u[vVBu=JSLnQO{}rV^G4\(@fQJ"];e5,lܰT*yiXG>Srq[݊Mw-ۼ=κ.bٚD`"q&c`hFnd8=T8ٮz9iNu(rgI>w{۾vo 8?=2ӘhUUbb- <| e}䫯ꦦ"Ge~=}{v~}+K=6Is|:Od /*(Ŏ (Rws&ɼdY|~bl7ׯ^~=P/|AAIÐȡFb6`e>{oZ]b߳ϓL&y-w n{^C+W\j:o:3I>_D7qcb׳ͬY mR"{7ٳ 'GC8="0uȲ|<#|`l=s)GSxS BV8P(TJ_<3 ѧlV— TStBkN? ~/{ClR*L*j*q4z8p ,Mȃ65<9ȁ#Y= |.~+!H4}x{Nʼi%- >Ճ@_C޾.im7l:KBO? : *FRth,p}2Rb`o x/祩Sƿ`)HU<0 ʗ "f5#ӑe6bK/4J} '4fS_Ў* &b16IYTnzG,87^ hԸP`i*5ݠ.#>w.yi!|_Xoੳ1L>檆L{a43tr8`AvVܑbI~BLHX$!A|-5ND 0 #include #include #include extern QSettings* g_gui_state; IncomingCallPopup::IncomingCallPopup(QObject *parent) : QObject(parent) { m_view = new QQuickView; m_view->setFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::ToolTip); m_view->rootContext()->setContextProperty("viewerWidget", m_view); m_view->setSource(QUrl("qrc:/qml/incoming_call.qml")); // Place into the middle of the screen positionWindow(); QObject* button; button = m_view->rootObject()->findChild("buttonAnswer"); connect(button, SIGNAL(clicked()), this, SLOT(onAnswerClicked())); button = m_view->rootObject()->findChild("buttonReject"); connect(button, SIGNAL(clicked()), this, SLOT(onRejectClicked())); m_callerText = m_view->rootObject()->findChild("callerText"); connect(m_view->rootObject(), SIGNAL(moved()), this, SLOT(saveState())); } IncomingCallPopup::~IncomingCallPopup() { delete m_view; } void IncomingCallPopup::positionWindow() { QDesktopWidget* desktop = qApp->desktop(); int x, y; int defaultX, defaultY; defaultX = desktop->width()/2 - m_view->width()/2; defaultY = desktop->height()/2 - m_view->height()/2; x = g_gui_state->value("incoming_popup/x", defaultX).toInt(); y = g_gui_state->value("incoming_popup/y", defaultY).toInt(); // Reset position if off screen if (x > desktop->width() || x < 0) x = defaultX; if (y > desktop->height() || y < 0) y = defaultY; m_view->setPosition(x, y); } void IncomingCallPopup::saveState() { QPoint pos = m_view->position(); g_gui_state->setValue("incoming_popup/x", pos.x()); g_gui_state->setValue("incoming_popup/y", pos.y()); } void IncomingCallPopup::move(int x, int y) { m_view->setPosition(QPoint(x, y)); } void IncomingCallPopup::setCallerName(const QString& name) { QString text = tr("%1 calling").arg(name); m_callerText->setProperty("text", text); } void IncomingCallPopup::onAnswerClicked() { emit answerClicked(); m_view->hide(); } void IncomingCallPopup::onRejectClicked() { emit rejectClicked(); m_view->hide(); } void IncomingCallPopup::show() { m_view->show(); } void IncomingCallPopup::hide() { m_view->hide(); } twinkle-1.10.1/src/gui/incoming_call_popup.h000066400000000000000000000012441277565361200210330ustar00rootroot00000000000000#ifndef T_INCOMING_CALL_POPUP_H #define T_INCOMING_CALL_POPUP_H #include #include class IncomingCallPopup : public QObject { Q_OBJECT public: explicit IncomingCallPopup(QObject *parent = 0); virtual ~IncomingCallPopup(); void setCallerName(const QString& name); void show(); void hide(); void setVisible(bool v) { if (v) show(); else hide(); } void move(int x, int y); private: void positionWindow(); signals: void answerClicked(); void rejectClicked(); public slots: void onAnswerClicked(); void onRejectClicked(); void saveState(); private: QQuickView* m_view; QQuickItem* m_callerText; }; #endif // T_INCOMING_CALL_POPUP_H twinkle-1.10.1/src/gui/inviteform.cpp000066400000000000000000000101461277565361200175300ustar00rootroot00000000000000#include "inviteform.h" //Added by qt3to4: #include #include "gui.h" #include "util.h" #include "audits/memman.h" #include "sys_settings.h" #include #include /* Copyright (C) 2005-2009 Michel de Boer 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, see . */ InviteForm::InviteForm(QWidget *parent) : QDialog(parent) { setupUi(this); init(); } InviteForm::~InviteForm() { destroy(); } void InviteForm::init() { getAddressForm = 0; // Set toolbutton icons for disabled options. setDisabledIcon(addressToolButton, ":/icons/images/kontact_contacts-disabled.png"); // A QComboBox accepts a new line through copy/paste. QRegExp rxNoNewLine("[^\\n\\r]*"); inviteComboBox->setValidator(new QRegExpValidator(rxNoNewLine, this)); } void InviteForm::destroy() { if (getAddressForm) { MEMMAN_DELETE(getAddressForm); delete getAddressForm; } } void InviteForm::clear() { inviteComboBox->clearEditText(); subjectLineEdit->clear(); hideUserCheckBox->setChecked(false); inviteComboBox->setFocus(); } void InviteForm::show(t_user *user_config, const QString &dest, const QString &subject, bool anonymous) { ((t_gui *)ui)->fill_user_combo(fromComboBox); // Select from user if (user_config) { for (int i = 0; i < fromComboBox->count(); i++) { if (fromComboBox->itemText(i) == user_config->get_profile_name().c_str()) { fromComboBox->setCurrentIndex(i); break; } } } inviteComboBox->setEditText(dest); subjectLineEdit->setText(subject); hideUserCheckBox->setChecked(anonymous); QDialog::show(); } void InviteForm::validate() { string display, dest_str; t_user *from_user = phone->ref_user_profile( fromComboBox->currentText().toStdString()); ui->expand_destination(from_user, inviteComboBox->currentText().trimmed().toStdString(), display, dest_str); t_url dest(dest_str); if (dest.is_valid()) { addToInviteComboBox(inviteComboBox->currentText()); emit raw_destination(inviteComboBox->currentText()); emit destination(from_user, display.c_str(), dest, subjectLineEdit->text(), hideUserCheckBox->isChecked()); accept(); } else { inviteComboBox->setFocus(); inviteComboBox->lineEdit()->selectAll(); } } // Add a destination to the history list of inviteComboBox void InviteForm::addToInviteComboBox(const QString &destination) { inviteComboBox->insertItem(0, destination); if (inviteComboBox->count() > SIZE_REDIAL_LIST) { inviteComboBox->removeItem(inviteComboBox->count() - 1); } } void InviteForm::reject() { // Unseize the line ((t_gui *)ui)->action_unseize(); QDialog::reject(); } void InviteForm::closeEvent(QCloseEvent *) { reject(); } void InviteForm::showAddressBook() { if (!getAddressForm) { getAddressForm = new GetAddressForm(this); getAddressForm->setModal(true); MEMMAN_NEW(getAddressForm); } connect(getAddressForm, SIGNAL(address(const QString &)), this, SLOT(selectedAddress(const QString &))); getAddressForm->show(); } void InviteForm::selectedAddress(const QString &address) { inviteComboBox->setEditText(address); } void InviteForm::warnHideUser(void) { // Warn only once if (!sys_config->get_warn_hide_user()) return; QString msg = tr("Not all SIP providers support identity hiding. Make sure your SIP provider " "supports it if you really need it."); ((t_gui *)ui)->cb_show_msg(this, msg.toStdString(), MSG_WARNING); // Do not warn again sys_config->set_warn_hide_user(false); } twinkle-1.10.1/src/gui/inviteform.h000066400000000000000000000016361277565361200172010ustar00rootroot00000000000000#ifndef INVITEFORM_UI_H #define INVITEFORM_UI_H #include "ui_inviteform.h" #include "sockets/url.h" #include "getaddressform.h" #include "user.h" #include "phone.h" #include class t_phone; extern t_phone *phone; class InviteForm : public QDialog, public Ui::InviteForm { Q_OBJECT public: InviteForm(QWidget *parent); ~InviteForm(); public slots: void clear(); void show( t_user * user_config, const QString & dest, const QString & subject, bool anonymous ); void validate(); void addToInviteComboBox( const QString & destination ); void reject(); void closeEvent( QCloseEvent * ); void showAddressBook(); void selectedAddress( const QString & address ); void warnHideUser( void ); signals: void destination(t_user *, const QString &, const t_url &, const QString &, bool); void raw_destination(const QString &); private: void init(); void destroy(); GetAddressForm *getAddressForm; }; #endif twinkle-1.10.1/src/gui/inviteform.ui000066400000000000000000000247631277565361200173750ustar00rootroot00000000000000 InviteForm 0 0 592 203 5 5 0 0 Twinkle - Call &To: inviteComboBox false 20 23 QSizePolicy::Expanding Qt::Vertical Optionally you can provide a subject here. This might be shown to the callee. Qt::TabFocus F10 kontact_contacts.png Address book Select an address from the address book. 20 20 QSizePolicy::Expanding Qt::Vertical 7 0 0 0 true 10 QComboBox::NoInsert true The address that you want to call. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. 7 0 0 0 The user that will make the call. &Subject: subjectLineEdit false &From: fromComboBox false &Hide identity Alt+H <p> With this option you request your SIP provider to hide your identity from the called party. This will only hide your identity, e.g. your SIP address, telephone number. It does <b>not</b> hide your IP address. </p> <p> <b>Warning:</b> not all providers support identity hiding. </p> 181 20 QSizePolicy::Expanding Qt::Horizontal 20 20 QSizePolicy::Expanding Qt::Vertical 91 20 QSizePolicy::Expanding Qt::Horizontal &OK true &Cancel inviteComboBox subjectLineEdit hideUserCheckBox addressToolButton okPushButton cancelPushButton fromComboBox qstring.h sockets/url.h ui_getaddressform.h user.h phone.h cancelPushButton clicked() InviteForm reject() okPushButton clicked() InviteForm validate() addressToolButton clicked() InviteForm showAddressBook() hideUserCheckBox clicked() InviteForm warnHideUser() twinkle-1.10.1/src/gui/lang/000077500000000000000000000000001277565361200155615ustar00rootroot00000000000000twinkle-1.10.1/src/gui/lang/twinkle_cs.ts000066400000000000000000010207611277565361200203020ustar00rootroot00000000000000 AddressCardForm Twinkle - Address Card Twinkle - Adresářový záznam &Remark: P&oznámka: Infix name of contact. Prostřední jméno. First name of contact. Křestní jméno nebo jakékoliv jiné jméno. Bude třídicím klíčem. &First name: &Křestní jméno: You may place any remark about the contact here. Políčko pro libovolné poznámky. &Phone: &Telefon: &Infix name: &Prostřední jméno: Phone number or SIP address of contact. Telefonní číslo nebo SIP adresa kontaktu. Last name of contact. Příjmení kontaktu. &Last name: &Příjmení: &OK &OK Alt+O Alt+O &Cancel &Zrušit Alt+C Alt+Z You must fill in a name. Musíte zadat jméno. You must fill in a phone number or SIP address. Musíte zadat telefonní číslo nebo SIP adresu. AddressTableModel Name Jméno Phone Telefon Remark Poznámka AuthenticationForm Twinkle - Authentication Twinkle - Přihlášení user No need to translate user The user for which authentication is requested. Uživatel, který má být přihlášen. profile No need to translate profile The user profile of the user for which authentication is requested. Profil uživatele, pro kterého je vyžadováno přihlášení. User profile: Uživatelský profil: User: Uživatel: &Password: &Heslo: Your password for authentication. Vaše přihlašovací heslo. Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Vaše přihlašovací SIP jméno. Často je identické s vaším uživatelským SIP jménem, ale může se i lišit. &User name: Uži&vatelské jméno: &OK &OK &Cancel &Zrušit Login required for realm: Přihlášení vyžadováno pro realm: realm No need to translate realm The realm for which you need to authenticate. Realm, ke kterému se musíte přihlásit. BuddyForm Twinkle - Buddy Twinkle - Kontakt Address book Adresář Select an address from the address book. Vybrat adresu z adresáře . &Phone: &Telefon: Name of your buddy. Jméno vašeho kontaktu. &Show availability Ukázat dostupno&st Alt+S Alt+S Check this option if you want to see the availability of your buddy. This will only work if your provider offers a presence agent. Vyberte tuto volbu, pokud chcete vidět dostupnost vytvořeného kontaktu. Toto bude fungovat, pouze pokud váš poskytovatel tuto funkčnost nabízí. &Name: &Jméno: SIP address your buddy. SIP adresa vašeho kontaktu. &OK &OK Alt+O Alt+O &Cancel &Zrušit Alt+C Alt+Z You must fill in a name. Musíte zadat jméno. Invalid phone. Neplatné telefonní číslo. Failed to save buddy list: %1 Nepodařilo se uložit seznam kontaktů: %1 BuddyList Availability Dostupnost unknown neznámá offline offline online online request rejected požadavek odmítnut not published není zveřejněna failed to publish zveřejnění selhalho request failed požadavek selhal Click right to add a buddy. Pravým kliknutím přidáte kontakt. CoreAudio Failed to open sound card Nepodařilo se získat přístup ke zvukové kartě Failed to create a UDP socket (RTP) on port %1 Nepodařilo se vytvořit UDP socket (RTP) na portu %1 Failed to create audio receiver thread. Nepodařilo se vytvořit vlákno pro nahrávání zvuku. Failed to create audio transmitter thread. Nepodařilo se vytvořit vlákno pro přehrávání zvuku. CoreCallHistory local user lokální uživatel remote user vzdálený uživatel failure chyba unknown neznámý in příchozí out odchozí DeregisterForm Twinkle - Deregister Twinkle - Odhlášení deregister all devices odhlásit všechna zařízení &OK &OK &Cancel &Zrušit DiamondcardProfileForm &Cancel Zrušit (Es&c) Fill in your account ID. Fill in your PIN code. A user profile with name %1 already exists. DtmfForm Twinkle - DTMF Twinkle - DTMF Keypad Numerická klávesnice 2 2 3 3 Over decadic A. Normally not needed. Funkční klávesa A. Používaná zřídka. 4 4 5 5 6 6 Over decadic B. Normally not needed. Funkční klávesa B. Používaná zřídka. 7 7 8 8 9 9 Over decadic C. Normally not needed. Funkční klávesa C. Používaná zřídka. Star (*) Hvězdička (*) 0 0 Pound (#) Křížek (#) Over decadic D. Normally not needed. Funční klávesa D. Používaná zřídka. 1 1 &Close &Zavřít Alt+C Alt+C FreeDeskSysTray Show/Hide Ukázat/Zminimalizovat Quit Ukončit GUI Failed to create a UDP socket (SIP) on port %1 Chyba při otevírání UDP socketu (SIP) na portu %1 The following profiles are both for user %1 Následující uživatelské profily používají stejnou SIP adresu %1 You can only run multiple profiles for different users. Na jeden SIP účet si nemůžete současně aktivovat více profilů. Cannot find a network interface. Twinkle will use 127.0.0.1 as the local IP address. When you connect to the network you have to restart Twinkle to use the correct IP address. Twinkle nemůže najít žádné aktivní síťové rozhraní a používá nyní 127.0.0.1 jako svoji lokální IP adresu. Pokud se připojíte k nějaké síti později, musíte Twinkle spustit znovu, aby používal správnou IP adresu. Line %1: incoming call for %2 Linka %1: příchozí hovor pro %2 Call transferred by %1 Volání přepojeno uživatelem %1 Line %1: far end cancelled call. Linka %1: protistrana přerušila hovor. Line %1: far end released call. Linka %1: hovor ukončen protistranou. Line %1: SDP answer from far end not supported. Linka %1: SDP odpověď protistrany není podporována. Line %1: SDP answer from far end missing. Linka %1: žádná SDP odpověď protistrany. Line %1: Unsupported content type in answer from far end. Linka %1: Typ obsahu v odpovědi protistrany není podporován. Line %1: no ACK received, call will be terminated. Linka %1: žádný ACK od protistrany, volání bude ukončeno. Line %1: no PRACK received, call will be terminated. Linka %1: žádný PRACK od protistrany, volání bude ukončeno. Line %1: PRACK failed. Linka %1: PRACK selhalo. Line %1: failed to cancel call. Linka %1: chyba při pokusu o ukončení hovoru. Line %1: far end answered call. Linka %1: protistrana přijala hovor. Line %1: call failed. Linka %1: volání selhalo. The call can be redirected to: Hovor může být přesměrován na: Line %1: call released. Linka %1: hovor ukončen. Line %1: call established. Linka %1: spojení navázáno. Response on terminal capability request: %1 %2 Odpověď protistrany na dotaz o výpis schopností: %1 %2 Terminal capabilities of %1 Schopnosti protistrany %1 Accepted body types: Akceptované "body types": unknown neznámý Accepted encodings: Akceptovaná kódování: Accepted languages: Akceptované jazyky: Allowed requests: Povolené požadavky: Supported extensions: Podporovaná rozšíření: none žádný End point type: Typ koncového zařízení: Line %1: call retrieve failed. Linka %1: chyba při pokusu o obnovení hovoru. %1, registration failed: %2 %3 %1, neúspěšné přihlášení: %2 %3 %1, registration succeeded (expires = %2 seconds) %1, registrace úspěšná (platná na %2 sekund) %1, registration failed: STUN failure %1, registrace selhala: chyba STUN %1, de-registration succeeded: %2 %3 %1, úspěšné odhlášení: %2 %3 %1, fetching registrations failed: %2 %3 %1, chyba při dotazu na registrace: %2 %3 : you are not registered : nejste registrován : you have the following registrations : jsou aktivní následující registrace : fetching registrations... : probíhá dotaz na registrace... Line %1: redirecting request to Linka %1: požadavek přesměrováván na Redirecting request to: %1 Přesměrovat požadavek na: %1 Line %1: DTMF detected: Linka %1: detekováno DTMF: invalid DTMF telephone event (%1) neplatná událost DTMF (%1) Line %1: send DTMF %2 Linka %1: odesílá se DTMF %2 Line %1: far end does not support DTMF telephone events. Linka %1: protistrana nepodporuje události DTMF. Line %1: received notification. Linka %1: přijata notifikace. Event: %1 Událost: %1 State: %1 Stav: %1 Reason: %1 Důvod: %1 Progress: %1 %2 Průběh: %1 %2 Line %1: call transfer failed. Linka %1: přepojení hovoru selhalo. Line %1: call successfully transferred. Linka %1: hovor byl přepojen. Line %1: call transfer still in progress. Linka %1: přepojení hovoru stále probíhá. No further notifications will be received. Nebudou přijímány další notifikace. Line %1: transferring call to %2 Linka %1: hovor se přepojuje na %2 Transfer requested by %1 Přepojení vyžádal %1 Line %1: Call transfer failed. Retrieving original call. Linka %1: Přepojení hovoru selhalo. Obnovuji původní hovor. Redirecting call Přesměrovávám hovor User profile: Uživatelský profil: User: Uživatel: Do you allow the call to be redirected to the following destination? Souhlasíte, aby hovor byl přesměrován na následující cíl? If you don't want to be asked this anymore, then you must change the settings in the SIP protocol section of the user profile. Pokud nechcete, abyste byl nadále dotazován, musíte změnit nastavení v sekci protokol SIP v uživatelském profilu. Redirecting request Přesměrovávám požadavek Do you allow the %1 request to be redirected to the following destination? Má být požadavek %1 přesměrován na následující destinaci? Transferring call Přepojování hovoru Request to transfer call received from: Požadavek na přepojení hovoru přijat od: Do you allow the call to be transferred to the following destination? Povolit přepojení hovoru k následujícímu cíli? Info: Info: Warning: Varování: Critical: Kritické: Firewall / NAT discovery... Detekce firewallu / NATu... Abort Přerušit Line %1 Linka %1 Click the padlock to confirm a correct SAS. Pro potvrzení správného SAS hesla klikněte na symbol zámečku. The remote user on line %1 disabled the encryption. Protistrana na lince %1 vypnula zašifrování. Line %1: SAS confirmed. Linka %1: SAS potvrzeno. Line %1: SAS confirmation reset. Linka %1: SAS potvrzení smazáno. Line %1: call rejected. Linka %1: hovor odmítnut. Line %1: call redirected. Linka %1: hovor přesměrován. Failed to start conference. Nepodařilo se zahájit konferenci. Override lock file and start anyway? Ignorovat soubor se zámkem a přesto spustit? %1, STUN request failed: %2 %3 %1, STUN dotaz selhal: %2 %3 %1, STUN request failed. %1, STUN diatz selhal. %1, voice mail status failure. %1, chyba stavu hlasové schránky. %1, voice mail status rejected. %1, odmítnut stav hlasové schránky. %1, voice mailbox does not exist. %1, hlasová schránka neexistuje. %1, voice mail status terminated. %1, ukončen přenos z hlasové schránky. %1, de-registration failed: %2 %3 %1, deregistrace selhala: %2 %3 Request to transfer call received. Přijat požadavek na přepojení hovoru. If these are users for different domains, then enable the following option in your user profile (SIP protocol) Pokud jsou toto uživatelé pro různé domény, potom aktivujte následující volbu ve vašem uživatelském profilu (Protokol SIP) Use domain name to create a unique contact header Použít doménové jméno k vytvoření jedinečné kontaktní hlavičky Failed to create a %1 socket (SIP) on port %2 Selhalo vytvoření %1 socketu (SIP) na portu %2 Accepted by network Akceptováno sítí Failed to save message attachment: %1 Selhalo uložení přílohy zprávy: %1 Transferred by: %1 Přepojil: %1 Cannot open web browser: %1 Nemohu otevřit webový prohlížeč: %1 Configure your web browser in the system settings. Nastavte svůj webový prohlížeč v systémovém nastavení. GetAddressForm Twinkle - Select address Twinkle - Výběr adresy Name Jméno Type Typ Phone Telefon &Show only SIP addresses Zobrazit pouze &SIP adresy Alt+S Alt+S Check this option when you only want to see contacts with SIP addresses, i.e. starting with "<b>sip:</b>". Pokud je aktivováno, budou zobrazeny pouze kontakty, které obsahují platnou SIP adresu (začínající na <b>sip:</b>). &Reload Aktua&lizovat Alt+R Alt+L Reload the list of addresses from KAddressbook. Znovu načíst seznam adres z KAddressbook. &OK &OK Alt+O Alt+O &Cancel &Zrušit Alt+C Alt+Z &KAddressBook &KAddressBook This list of addresses is taken from <b>KAddressBook</b>. Contacts for which you did not provide a phone number are not shown here. To add, delete or modify address information you have to use KAddressBook. Tento seznam kontaktů pochází z <b>KAddressbook</b>. Kontakty, které neobsahují telefonní číslo nebo SIP adresu zde nejsou uvedeny. K vytvoření nebo úpravě kontaktů použijte program KAddressbook. &Local address book &Místní adresář Remark Poznámka Contacts in the local address book of Twinkle. Kontakty v lokálním adresáři Twinkle. &Add Přid&at Alt+A Alt+A Add a new contact to the local address book. Založit v místním adresáři nový kontakt. &Delete Smaza&t Alt+D Alt+T Delete a contact from the local address book. Smazat vybraný kontakt z místního adresáře. &Edit &Upravit Alt+E Alt+U Edit a contact from the local address book. Upravit vybraný kontakt v místním adresáři. <p>You seem not to have any contacts with a phone number in <b>KAddressBook</b>, KDE's address book application. Twinkle retrieves all contacts with a phone number from KAddressBook. To manage your contacts you have to use KAddressBook.<p>As an alternative you may use Twinkle's local address book.</p> Zdá se, že <p><b>KAddressbook</b> neobsahuje žádné záznamy s telefonními čísly, které by Twinkle mohl načíst. Použijte prosím tento program k úpravě nebo zanesení vašich kontaktů.</p> <p>Druhou možností je používat místní adresář v Twinkle.</p> GetProfileNameForm Twinkle - Profile name Twinkle - Název profilu &OK &OK &Cancel &Zrušit Enter a name for your profile: Zadejte název profilu: <b>The name of your profile</b> <br><br> A profile contains your user settings, e.g. your user name and password. You have to give each profile a name. <br><br> If you have multiple SIP accounts, you can create multiple profiles. When you startup Twinkle it will show you the list of profile names from which you can select the profile you want to run. <br><br> To remember your profiles easily you could use your SIP user name as a profile name, e.g. <b>example@example.com</b> <b>Název vašeho profilu</b> <br><br> Profil obsahuje všechna uživatelská nastavení, např. uživatelské jméno SIP, heslo. Každý profil musí být pojmenován. <br><br> Pokud máte vícero účtů SIP, můžete si vytvořit několik profilů. Při spuštění vám Twinkle zobrazí seznam názvů profilů, ze kterých si můžete vybrat ty, které chcete spustit. <br><br> Ke snadnému zapamatování si profilu je možné použít k označení profilu uživatelské jméno, např. <b>example@example.com</b> </p> Cannot find .twinkle directory in your home directory. Nelze najít adresář .twinkle ve vašem domovském adresáři. Profile already exists. Profil s tímto názvem již existuje. Rename profile '%1' to: Přejmenovat profil %1 na: HistoryForm Twinkle - Call History Twinkle - Seznam volání Time Čas In/Out Příchozí/Odchozí From/To Protistrana Subject Předmět Status Stav Call details Podrobnosti hovoru Details of the selected call record. Detaily k vybranému hovoru. View Zobrazit &Incoming calls &Příchozí hovory Alt+I Alt+I Check this option to show incoming calls. Zaškrtněte tuto volbu pro zobrazení příchozích hovorů. &Outgoing calls &Odchozí hovory Alt+O Alt+O Check this option to show outgoing calls. Zaškrtněte tuto volbu pro zobrazení odchozích hovorů. &Answered calls &Přijaté hovory Alt+A Alt+A Check this option to show answered calls. Zaškrtněte tuto volbu pro zobrazení přijatých hovorů. &Missed calls &Zmeškané hovory Alt+M Alt+M Check this option to show missed calls. Zaškrtněte tuto volbu pro zobrazení zmeškaných hovorů. Current &user profiles only &Pouze aktivní uživatelské profily Alt+U Alt+U Check this option to show only calls associated with this user profile. Pokud je aktivováno, budou zobrazeny jen hovory, které byly provedeny pod tímto uživatelským profilem. C&lear &Smazat seznam Alt+L Alt+L <p>Clear the complete call history.</p> <p><b>Note:</b> this will clear <b>all</b> records, also records not shown depending on the checked view options.</p> <p>Smazat celý protokol volání.</p> <p><b>Poznámka:</b> Tímto dojde ke smazání všech záznamů. Včetně těch, které nejsou zobrazeny dle zvolených parametrů v nastavení.</p> Alt+C Alt+S Close this window. Zavřít toto okno. Call start: Volání zahájeno: Call answer: Na volání odpovězeno: Call end: Hovor ukončen: Call duration: Délka hovoru: Direction: Směr: From: Od: To: Kam: Reply to: Odpovědět na: Referred by: Přes: Subject: Předmět: Released by: Ukončil: Status: Status: Far end device: Zařízení protistrany: User profile: Uživatelský profil: conversation rozhovor Call... Volat... Delete Smazat Re: Odp: Clo&se Za&vřít Alt+S Alt+S &Call &Volat Call selected address. Volat vybranou adresu. Number of calls: Počet hovorů: ### ### Total call duration: Celkové trvání hovorů: IncomingCallPopup %1 calling %1 volá InviteForm Twinkle - Call Twinkle - Volání &To: K&am: Optionally you can provide a subject here. This might be shown to the callee. Zde můžete volitelně zadat předmět hovoru, který může být zobrazen volanému. Address book Adresář Select an address from the address book. Vybrat adresu z adresáře. The address that you want to call. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Adresa, na kterou chcete zavolat. Toto může být plnohodnotná SIP adresa jako <b>sip:example@example.com</b> nebo jen uživatel nebo telefonní číslo. Pokud není zadaná úplná adresa, Twinkle ji doplní o doménové jméno aktuálního uživatelského profilu. The user that will make the call. Uživatel, který uskuteční hovor. &Subject: &Předmět: &From: &Od: &OK &OK &Cancel &Zrušit &Hide identity &Skrýt identitu volajícího Alt+H Alt+H <p> With this option you request your SIP provider to hide your identity from the called party. This will only hide your identity, e.g. your SIP address, telephone number. It does <b>not</b> hide your IP address. </p> <p> <b>Warning:</b> not all providers support identity hiding. </p> <p>S touto volbou dáváte najevo vašemu SIP poskytovateli, že nechcete aby byly na protistranu zaslánu informace o vaší identitě. Např. vaše SIP adresa nebo telefonní číslo. Nicméně vaše IP adresa bude protistraně <b>vždy</b> sdělena.</p> <p><b>Upozornění: </b>Tuto možnost nenenabízejí všichni poskytovatelé!</p> Not all SIP providers support identity hiding. Make sure your SIP provider supports it if you really need it. Ne všichni poskytovatelé SIP umožňují skrytí identity. Pokud funkci skutečně potřebujete, ujistěte se, že ji váš poskytovatel SIP nabízí. F10 F10 LogViewForm Twinkle - Log Twinkle - Log Contents of the current log file (~/.twinkle/twinkle.log) Obsah aktuálního logu (~/.twinkle/twinkle.log) &Close &Zavřít Alt+C Alt+C C&lear &Vymazat Alt+L Alt+V Clear the log window. This does <b>not</b> clear the log file itself. Vyčistit okno s logem. Obsah samotného souboru s logem smazán <b>nebude</b>. MessageForm Twinkle - Instant message Twinkle - textová zpráva &To: &Komu: The user that will send the message. Uživatel, který pošle zprávu. The address of the user that you want to send a message. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Adresa uživatele, kterému má být zpráva poslána. To může být buď SIP adresa jako <b>sip:example@example.com</b> nebo jen uživatel nebo telefonní číslo z úplné adresy. Pokud se neuvede celá adresa, Twinkle doplní adresu doménou z uživatelského profilu. Address book Adresář Select an address from the address book. Výběr adresy z adresáře. &User profile: &Uživatelský profil: Conversation Konverzace Type your message here and then press "send" to send it. Sem napište zprávu a poté pro odeslání stiskněte "Odeslat". &Send &Odeslat Alt+S Send the message. Odeslat zprávu. Delivery failure Doručení selhalo Delivery notification Potvrzení o doručení Instant message toolbar Lišta s instantními zprávami Send file... Odeslat soubor... Send file Odeslat soubor image size is scaled down in preview obrázek je v náhledu zmenšen Open with %1... Otevřít s %1... Open with... Otevřít s... Save attachment as... Uložit přílohu jako... File already exists. Do you want to overwrite this file? Soubor již existuje. Chcete přepsat tento soubor? Failed to save attachment. Uložení přílohy selhalo. %1 is typing a message. %1 píše zprávu. F10 F10 Size Velikost MessageFormView sending message odesílání zprávy MphoneForm Twinkle Twinkle &Call: Label in front of combobox to enter address &Volané číslo: The address that you want to call. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Adresa, na kterou chcete zavolat. Může to být úplné SIP adresa jako <b>sip:example@example.com</b> nebo jen uživatel nebo telefonní číslo z úplné adresy. Pokud není zadána celá adresa, Twinkle doplní chybějící část doménovým jménem z vašeho uživatelského profilu. The user that will make the call. Uživatel, který uskuteční volání. &User: &Uživatel: Dial Volat Dial the address. Volat adresu. Address book Adresář Select an address from the address book. Vybrat adresu z adresáře. Auto answer indication. Indikátor automatického příjmu hovoru. Call redirect indication. Indikátor přesměrování hovoru. Do not disturb indication. Indikátor stavu nerušit. Missed call indication. Indikátor zmeškaných hovorů. Registration status. Stav registrace. Display Stavové hlášky Line status Stav linky Line &1: Linka &1: Alt+1 Alt+1 Click to switch to line 1. Klikněte pro přepnutí na linku 1. From: Od: To: Kam: Subject: Předmět: Visual indication of line state. Optické zobrazení stavu linky. idle No need to translate idle Call is on hold Hovor je podržen Voice is muted Hovor je ztišen Conference call Konferenční hovor Transferring call Hovor bude přesměrován <p> The padlock indicates that your voice is encrypted during transport over the network. </p> <h3>SAS - Short Authentication String</h3> <p> Both ends of an encrypted voice channel receive the same SAS on the first call. If the SAS is different at each end, your voice channel may be compromised by a man-in-the-middle attack (MitM). </p> <p> If the SAS is equal at both ends, then you should confirm it by clicking this padlock for stronger security of future calls to the same destination. For subsequent calls to the same destination, you don't have to confirm the SAS again. The padlock will show a check symbol when the SAS has been confirmed. </p> <p> Symbol zámečku se zobrazí pokud je volání přenášeno pomocí šifrování a není možné ho odposlouchávat. </p> <h3>SAS - Short Authentication String</h3> <p> Oběma účastníkům zašifrovaného hovoru bude při prvním kontaktu doručena tzv. SAS značka. Porovnáním této značky při každém dalším volání s toutéž protistranou lze zjistit, v případě že by se kód změnil, že dochází k odposlouchávání hovoru. Šlo by o tzv. "man-in-the-middle attack". </p> <p> Pokud je SAS shodný na obou stranách, klikněte na ikonku zámečku. Lze se o tom přesvědčit dotazem na tuto značku u volaného. Při každém dalším volání na takto označený kontakt bude jeho identita automaticky ověřena a výsledek bude zobrazen ve formě zatržítka na symbolu zámečku. </p> <p>Opětovným kliknutím na ikonku zámečku se zatržítkem dojde ke smazání ověřovací značky SAS a k její nové aktivaci je nutné provést její nové vygenerování.</p> sas No need to translate sas Short authentication string Krátký ověřovací řetězec g711a/g711a No need to translate Audio codec Audio kodek 0:00:00 0:00:00 Call duration Trvání hovoru sip:from No need to translate sip:to No need to translate subject No need to translate photo No need to translate Line &2: Linka &2: Alt+2 Alt+2 Click to switch to line 2. Klikněte pro přepnutí na linku 2. &File &Soubor &Edit &Upravit C&all &Hovor Activate line Vybrat linku &Registration &Registrace &Services &Služby &View &Zobrazit &Help &Nápověda Call Toolbar Lišta volání Quit Ukončit &Quit &Ukončit Ctrl+Q Ctrl+Q About Twinkle O programu Twinkle &About Twinkle O &programu Twinkle Call someone Zavolat někomu F5 F5 Answer incoming call Přijmout příchozí hovor F6 F6 Release call Ukončit hovor Reject incoming call Odmítnout příchozí hovor F8 F8 Put a call on hold, or retrieve a held call Podržet hovor nebo pokračovat v podrženém hovoru Redirect incoming call without answering Přesměrovat příchozí hovor bez přijetí hovoru Open keypad to enter digits for voice menu's Otevřít numerickou klávesnici pro hlasová menu Register Registrovat se &Register &Registrovat se Deregister Deregistrovat se &Deregister &Deregistrovat se Deregister this device Deregistrovat toto zařízení Show registrations Zobrazit registrace &Show registrations &Zobrazit registrace Terminal capabilities Parametry protistrany Request terminal capabilities from someone Dotaz na parametry protistrany Do not disturb Nerušit &Do not disturb &Nerušit Call redirection Přesměrování hovoru Call &redirection... &Přesměrování hovoru... Repeat last call Opakované vytáčení F12 F12 About Qt O Qt About &Qt O &Qt User profile Uživatelský profil &User profile... &Uživatelský profil... Join two calls in a 3-way conference Spojit dva hovory do konference Mute a call Vypnout mikrofon Transfer call Přesměrovat hovor System settings Systémová nastavení &System settings... &Systémová nastavení... Deregister all Odhlásit vše Deregister &all &Odhlásit vše Deregister all your registered devices Odhlásit všechna registrovaná zařízení Auto answer Automaticky přijmout volání &Auto answer &Automaticky přijmout volání Log Log &Log... &Log... Call history Seznam volání Call &history... &Seznam volání... F9 F9 Change user ... Změnit uživatele ... &Change user ... &Změnit uživatele ... Activate or de-activate users Aktivovat nebo deaktivovat uživatele What's This? Co je toto? What's &This? Co je &toto? Shift+F1 Shift+F1 Line 1 Linka 1 Line 2 Linka 2 idle volná dialing vytáčím attempting call, please wait pokus o navázání spojení, prosím čekejte incoming call příchozí volání establishing call, please wait navazuji hovor, prosím čekejte established navázáno established (waiting for media) navázáno (čeká se na zvuk) releasing call, please wait zavěšuji, prosím čekejte unknown state neznámý stav Voice is encrypted Hovor je zašifrován Click to confirm SAS. Klikněte pro potvrzení SAS. Click to clear SAS verification. Klikněte pro smazání ověření SAS. User: Uživatel: Call: Hovor: Registration status: Stav registrace: Registered Registrován Failed Nezdařilo se Not registered Neregistrován No users are registered. Není přihlášen žádný uživatel. Do not disturb active for: Nerušit aktivováno pro: Redirection active for: Přsměrování aktivováno pro: Auto answer active for: Automatické přijetí hovorů aktivováno pro: Do not disturb is not active. Nerušit není aktivní. Redirection is not active. Přesměrování volání není aktivováno. Auto answer is not active. Automatické přijetí hovorů není aktivní. You have no missed calls. Nemáte žádné zmeškané hovory. You missed 1 call. 1 zmeškaný hovor. You missed %1 calls. %1 zmeškaných hovorů. Click to see call history for details. Kliknutím se otevře podrobný seznam hovorů. Starting user profiles... Spouštím uživatelské profily... The following profiles are both for user %1 Následující uživatelské profily používají stejné uživatele %1 You can only run multiple profiles for different users. Různé profily musejí mít odlišné uživatele. You have changed the SIP UDP port. This setting will only become active when you restart Twinkle. Byl změněn SIP UDP port. Toto nastavení bude aktivní až při příštím spuštění programu Twinkle. Esc Esc Transfer consultation Asistované přesměrování Hide identity Skrýt identitu Click to show registrations. Kliknutím se zobrazí registrace. %1 new, 1 old message %1 nová, 1 stará zpráva %1 new, %2 old messages %1 nové, %2 staré zprávy 1 new message 1 nová zpráva %1 new messages %1 nových zpráv 1 old message 1 stará zpráva %1 old messages %1 starých zpráv Messages waiting Přijatých zpráv No messages Žádné zprávy <b>Voice mail status:</b> <b>Stav hlasové schránky:</b> Failure Chyba Unknown Neznámý Click to access voice mail. Kliknutím se vstoupí do hlasové schránky. Click to activate/deactivate Kliknutím aktivovat / deaktivovat Click to activate Kliknutím aktivovat not provisioned není poskytováno You must provision your voice mail address in your user profile, before you can access it. Předtím než může být hlasová schránka používána, je nutné nastavit její adresu v uživatelském profilu. The line is busy. Cannot access voice mail. Hlasovou schránku nelze otevřít. Linka je obsazená. The voice mail address %1 is an invalid address. Please provision a valid address in your user profile. Adresa hlasové schránky "%1" je neplatná. Zkontrolujte nastavení ve vašem uživatelském profilu. Call toolbar text Volat &Call... call menu text Volat (&Call)... Answer toolbar text Odpovědět &Answer menu text &Odpovědět Bye toolbar text Zavěsit &Bye menu text Zavěsit (&Bye) Reject toolbar text Odmítnout &Reject menu text &Odmítnout Hold toolbar text Podržet &Hold menu text &Podržet Redirect toolbar text Přesměrovat R&edirect... menu text &Přesměrovat... Dtmf toolbar text DTMF &Dtmf... menu text &DTMF... &Terminal capabilities... menu text &Parametry protistrany... Redial toolbar text Opakovat &Redial menu text &Opakovat Conf toolbar text Konference &Conference menu text &Konference Mute toolbar text Ztišit &Mute menu text &Ztišit Xfer toolbar text Zprostředkovat Trans&fer... menu text &Zprostředkovat... Message waiting indication. Zobrazení čekajících zpráv. Voice mail Hlasová schránka &Voice mail &Hlasová schránka Access voice mail Vstoupit do hlasové schránky F11 Buddy list Seznam kontaktů &Message &Zpráva Msg Msg Instant &message... Textová &zpráva... Instant message Textová zpráva &Call... &Volat... &Edit... &Upravit... &Delete &Smazat O&ffline O&ffline &Online &Online &Change availability &Změnit dostupnost &Add buddy... &Přidat kontakt... Failed to save buddy list: %1 Selhalo uložení seznamu kontaktů: %1 You can create a separate buddy list for each user profile. You can only see availability of your buddies and publish your own availability if your provider offers a presence server. Je možné vytvořit oddělený seznam kontaktů pro každý uživatelský profil. Dostupnost vašich kontaktů a vlastní dostupnost lze zjistit a využívat jen pokud toto váš poskytovatel podporuje. &Buddy list &Seznam kontaktů &Display &Stavové hlášky F10 F10 Diamondcard Manual Příručka &Manual &Příručka Sign up &Sign up... Recharge... Balance history... Call history... Admin center... Recharge Balance history Admin center Call Volat &Answer Přijm&out Answer Přijmout &Bye &Zavěsit Bye Zavěsit &Reject &Odmítnout Reject Odmítnout &Hold &Podržet Hold Podržet R&edirect... &Přesměrovat... Redirect Přesměrovat &Dtmf... &DTMF... Dtmf DTMF &Terminal capabilities... &Parametry protistrany... &Redial &Opakovat Redial Opakovat &Conference &Konference Conf Konference &Mute &Ztišit Mute Ztišit Trans&fer... Přep&ojit... Xfer Přepojit NumberConversionForm Twinkle - Number conversion Twinkle - konverze tel. čísla &Match expression: &Hledaný výraz: &Replace: &Nahradit: Perl style format string for the replacement number. Formátovací řetězec (styl Perlu) s nahrazeným číslem. Perl style regular expression matching the number format you want to modify. Regulární výraz (styl Perlu) pro nahrazované číslo. &OK Alt+O Alt+O &Cancel &Zrušit Alt+C Alt+C Match expression may not be empty. Hledaný výraz nesmí být prázdný. Replace value may not be empty. Nahrazený text nesmí být prázdná. Invalid regular expression. Neplatný regulární výraz. RedirectForm Twinkle - Redirect Twinkle - Přesměrování Redirect incoming call to Příchozí hovor přeměrovat na You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. Pro přesměrování volání lze zadat max. 3 čísla. Pokud nebude hovor přijat prvním cílem, dojde k pokusu o přesměrování na druhý cíl atd. &3rd choice destination: &3. cíl: &2nd choice destination: &2. cíl: &1st choice destination: &1. cíl: Address book Adresář Select an address from the address book. Výběr adresy z adresáře. &OK &OK &Cancel &Zrušit F10 F10 F12 F12 F11 F11 SelectNicForm Twinkle - Select NIC Twinkle - výběr síťového rozhraní Select the network interface/IP address that you want to use: Vyberte síťové rozhraní / IP adresu, kterou chcete použít: You have multiple IP addresses. Here you must select which IP address should be used. This IP address will be used inside the SIP messages. Na vašem počítači je k dispozici více IP adres. Vyberte tu, pod kterou je váš počítač dostupný z internetu nebo pokud jste připojení na router vaši IP adresu v lokální síti. Tuto adresu bude Twinkle používat uvnitř datových SIP paketů jako adresu odesilatele. Set as default &IP &Nastavit jako výchozí IP adresu Alt+I Alt+I Make the selected IP address the default IP address. The next time you start Twinkle, this IP address will be automatically selected. Nastavit vybranou IP adresu jako výchozí. Při přístím startu Twinkle bude automaticky zvolena tato adresa. Set as default &NIC Nastavit jako &výchozí rozhraní Alt+N Alt+N Make the selected network interface the default interface. The next time you start Twinkle, this interface will be automatically selected. Nastavit vybrané síťové rozhraní jako výchozí. Při příštím startu Twinkle bude toto rozhraní automaticky zvoleno. &OK Alt+O Alt+O If you want to remove or change the default at a later time, you can do that via the system settings. Výchozí nastavení je možné změnit kdykoliv později v systémovém nastavení. SelectProfileForm Twinkle - Select user profile Twinkle - výběr uživatelského profilu Select user profile(s) to run: Zvolte uživatelské profily, které mají být aktivovány: User profile Uživatelský profil Tick the check boxes of the user profiles that you want to run and press run. Označte uživatelský profil, se kterým by měl Twinkle pracovat a stiskněte "Spustit". &New &nový Create a new profile with the profile editor. Pomocí editoru profilu založit nový uživatelský profil. &Wizard Průvodc&e Alt+W Alt+E Create a new profile with the wizard. Vytvořit nový uživatelský profil pomocí průvodce. &Edit U&pravit Alt+E Alt+P Edit the highlighted profile. Upravit vybraný uživatelský profil. &Delete &Smazat Alt+D Alt+S Delete the highlighted profile. Smazat vybraný uživatelský profil. Ren&ame &Přejmenovat Alt+A Alt+P Rename the highlighted profile. Das ausgewählte Benutzerprofil umbenennen. &Set as default Nastavit jako &výchozí Alt+S Alt+V Make the selected profiles the default profiles. The next time you start Twinkle, these profiles will be automatically run. Nastavit vybrané profily jako výchozí. Twinkle je použije automaticky při příštím startu. &Run &Spustit Alt+R Alt+S Run Twinkle with the selected profiles. Spustit twinkle s označenými uživatelskými profily. S&ystem settings S&ystémová nastavení Alt+Y Alt+Y Edit the system settings. Upravit systémová nastavení. &Cancel &Zrušit Alt+C Alt+Z <html>Before you can use Twinkle, you must create a user profile.<br>Click OK to create a profile.</html> <html>Předtím než můžete Twinkle začít používat, musíte založit aspoň jeden uživatelský profil.<br>Klikněte OK pro založení nového profilu.</html> <html>You can use the profile editor to create a profile. With the profile editor you can change many settings to tune the SIP protocol, RTP and many other things.<br><br>Alternatively you can use the wizard to quickly setup a user profile. The wizard asks you only a few essential settings. If you create a user profile with the wizard you can still edit the full profile with the profile editor at a later time.<br><br>Choose what method you wish to use.</html> <html>Pro vytvoření uživatelského profilu můžete použít editor profilu. Tento vám umožní změnit veškerá nastavení týkající se SIP protokolu, RTP jakož i dalších parametrů programu.<br><br>Popřípadě použijte Wizard pro rychlé a jednoduché nastavení základních paramtrů ke zvolenému uživatelskému profilu. Wizard se vás dotáže jen na nejzákladnější údaje, které vám váš SIP poskytovatel dá při zaregistrování. Pro některé poskytovatele vám budou dokonce některé z těchto údajů přímo wizardem nabídnuty. I přesto, že profil založíte pomocí wizardu ho budete moci později upravovat pomocí editoru.<br><br>Nápovědu získáte kdekoliv v Twinkle stiskem klávesové ombinace "Shift + F1", přes kontextovou nápovědu pomocí stisku pravého tlačítka na myši nebo stiskem na symbol "?" v pravém horním rohu okna.<br><br>Vyberte si jakým způsobem má být uživatelský profil založen.</html> <html>Next you may adjust the system settings. You can change these settings always at a later time.<br><br>Click OK to view and adjust the system settings.</html> <html>V dalším kroku můžete upravit systémová nastavení. Můžete tak učinit i kdykoliv později.<br><br>Klikněte na OK pro přístup k systémovým nastavením.</html> You did not select any user profile to run. Please select a profile. Nevybrali jste k použití žádný uživatelský profil. Vyberte prosím aspoň jeden profil. Are you sure you want to delete profile '%1'? Opravdu smazat uživatelský profil %1 ? Delete profile Smazat profil Failed to delete profile. Chyba při mazání profilu. Failed to rename profile. Chyba při přejmenování profilu. <p>If you want to remove or change the default at a later time, you can do that via the system settings.</p> <p>Výchozí nastavení je možné kdykoliv smazat nebo změnit v systémovém nastavení. </p> Cannot find .twinkle directory in your home directory. Nelze nalézt adresář .twinkle ve vašem domovském adresáři. &Profile editor &Editor profilu Create profile Vytvořit profil Ed&itor Ed&itace Alt+I Alt+I Dia&mondcard Alt+M Alt+M Modify profile Upravit profil Startup profile Profil při spuštění &Diamondcard Create a profile for a Diamondcard account. With a Diamondcard account you can make worldwide calls to regular and cell phones and send SMS messages. <html>You can use the profile editor to create a profile. With the profile editor you can change many settings to tune the SIP protocol, RTP and many other things.<br><br>Alternatively you can use the wizard to quickly setup a user profile. The wizard asks you only a few essential settings. If you create a user profile with the wizard you can still edit the full profile with the profile editor at a later time.<br><br> You can create a Diamondcard account to make worldwide calls to regular and cell phones and send SMS messages.<br><br> Choose what method you wish to use.</html> SelectUserForm Twinkle - Select user Twinkle - Výběr uživatele &Cancel &Zrušit Alt+C Alt+Z &Select all Vybrat &vše Alt+S Alt+V &OK Alt+O Alt+O C&lear all O&dznačit vše Alt+L Alt+D purpose No need to translate User Uživatel Register Přihlásit se Select users that you want to register. Vybrat uživatele k přihlášení. Deregister Odhlásit se Select users that you want to deregister. Vybrat uživatele k odhlášení. Deregister all devices Odhlásit všechna zařízení Select users for which you want to deregister all devices. Vybrat uživatele, u kterého mají být odhlášena všechna zařízení. Do not disturb Nerušit Select users for which you want to enable 'do not disturb'. Vybrat uživatele, pro který má být aktivován režim "Nerušit". Auto answer Automaticky přijmout volání Select users for which you want to enable 'auto answer'. Vybrat uživatelský profil, pro který má být aktivován režim "Automaticky přijmout volání". SendFileForm Twinkle - Send File Twinkle - Odeslat soubor Select file to send. Výběr souboru k odeslání. &File: &Soubor: &Subject: &Předmět: &OK &OK Alt+O Alt+O &Cancel &Zrušit Alt+C Alt+Z File does not exist. Soubor neexistuje. Send file... Odeslat soubor... SrvRedirectForm Twinkle - Call Redirection Twinkle - Přesměrování hovoru User: Uživatel: There are 3 redirect services:<p> <b>Unconditional:</b> redirect all calls </p> <p> <b>Busy:</b> redirect a call if both lines are busy </p> <p> <b>No answer:</b> redirect a call when the no-answer timer expires </p> Existují 3 způsoby přesměrování volání:<p> <b>Nepodmíněné:</b> přesměrovat všechny hovory </p> <p> <b>Obsazeno:</b> přesměrovat hovor, pokud jsou obě linky obsazené </p> <p> <b>Nepřijímá:</b> přesměrovat volání po uplynutí čekací prodlevy </p> &Unconditional N&epodmíněné &Redirect all calls &Přesměrovat všechna volání Alt+R Alt-R Activate the unconditional redirection service. Aktivovat službu přesměrovat všechna volání. Redirect to Přesměrovat na You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. Mohou být zadány až 3 cíle pro přesměrování volání. Pokud nebude hovor přebrán prvním cílem, bude použit druhý, popřípadě třetí. &3rd choice destination: &3. cíl: &2nd choice destination: &2. cíl: &1st choice destination: &1. cíl: Address book Adresář Select an address from the address book. Výběr adresy z adresáře. &Busy &Obsazeno &Redirect calls when I am busy &Přesměrovat volání, pokud jsou všechny linky obsazené Activate the redirection when busy service. Aktivovat přesměrovaní, pokud je linka nedostupná. &No answer &Neodpovídá &Redirect calls when I do not answer &Přesměrovat volání, pokud hovor nepřijímám Activate the redirection on no answer service. Aktivovat službu "Přesměrovat v nepřítomnosti". &OK Alt+O Accept and save all changes. Uložit změny. &Cancel &Zrušit Alt+C Alt+Z Undo your changes and close the window. Neukládat provedené změny a zavřít okno. You have entered an invalid destination. Neplatná cílová adresa. F10 F10 F11 F11 F12 F12 SysSettingsForm Twinkle - System Settings Twinkle - Systémová nastavení General Obecné Audio Audio Ring tones Vyzváněcí tóny Address book Adresář Network Síť Log Log Select a category for which you want to see or modify the settings. Vybrat skupinu vlastností, u které chcete změnit nastavení. Sound Card Zvuková karta Select the sound card for playing the ring tone for incoming calls. Vyberte zvukovou kartu pro přehrávání vyzváněcího tónu příchozího volání. Select the sound card to which your microphone is connected. Vyberte zvukovou kartu, ke které máte připojen mikrofon. Select the sound card for the speaker function during a call. Vyberte zvukovou kartu pro sluchátka nebo reproduktor. &Speaker: &Sluchátka/reproduktor: &Ring tone: &Vyzváněcí tón: Other device: Jiné zařízení: &Microphone: &Mikrofon: When using ALSA, it is not recommended to use the default device for the microphone as it gives poor sound quality. Při použítí ALSA rozhraní není doporučeno mít nastaveno pro mikrofon "standardní zařízení". Může to být příčinou špatné kvality zvuku. Reduce &noise from the microphone Speciální potlačení &rušení z mikrofonu Recordings from the microphone can contain noise. This could be annoying to the person on the other side of your call. This option removes soft noise coming from the microphone. The noise reduction algorithm is very simplistic. Sound is captured as 16 bits signed linear PCM samples. All samples between -50 and 50 are truncated to 0. Zvuk z mikrofonu může obsahovat rušení. Tato volba se ho snaží odstranit. Zavedena byla po zkušenosti s vadnými A/D převodníky u některých Provider Gateways. Algoritmus je velmi jednoduchý. Zvuk je navzorkován jako 16 bitový PCM vzorek a poté jsou všechny vzorky s hodnotou mezi -50 až 50 nastaveny na 0. Advanced Pokročilé nastavení OSS &fragment size: Velikost &fragmentů OSS: 16 32 64 128 256 The ALSA play period size influences the real time behaviour of your soundcard for playing sound. If your sound frequently drops while using ALSA, you might try a different value here. Nastavení přehrávací periody ALSA ovlivňuje zpoždění zvuku na zvukové kartě. Při problémech s vypadáváním nebo přeskakováním zvuku zkuste jinou hodnotu. ALSA &play period size: Velikost ALSA &periody přehrávání: &ALSA capture period size: Velikost &ALSA periody nahrávání: The OSS fragment size influences the real time behaviour of your soundcard. If your sound frequently drops while using OSS, you might try a different value here. Velikost fragmentu OSS ovlivňuje zpoždění zvuku na zvukové kartě. Při problémech s vypadáváním nebo přeskakováním zvuku zkuste jinou hodnotu. The ALSA capture period size influences the real time behaviour of your soundcard for capturing sound. If the other side of your call complains about frequently dropping sound, you might try a different value here. Velikost periody nahrávání ALSA ovlivňuje zpoždění zvuku. Pokud si protistrana stěžuje na časté výpadky zvuku, zkuste použít jinou hodnotu. &Max log size: &Maximální velikost logu: The maximum size of a log file in MB. When the log file exceeds this size, a backup of the log file is created and the current log file is zapped. Only one backup log file will be kept. Maximální velikost souboru s logem v MB. Při dosažení této velikosti je twinkle.log přejmenováno na twinkle.old. Uchovává se jen jeden starý soubor s logem. MB MB Log &debug reports Zapsat &hlášky pro ladění Alt+D ALt+H Indicates if reports marked as "debug" will be logged. Aktivuje zápis "debug" výstupů. Log &SIP reports Zapsat &SIP hlášky Alt+S Indicates if SIP messages will be logged. Aktivuje zápis SIP stavů. Log S&TUN reports Zapsat S&TUN hlášky Alt+T Indicates if STUN messages will be logged. Aktivuje zápis STUN stavů. Log m&emory reports Zapsat hlášky ohl&edně paměti Alt+E Indicates if reports concerning memory management will be logged. Aktivuje zápis protokolů o správě paměti. System tray Systémová lišta Create &system tray icon on startup Při spuštění vytvořit &ikonu v systémové liště Enable this option if you want a system tray icon for Twinkle. The system tray icon is created when you start Twinkle. Povolí ikonu Twinkle v systémové liště. Ta se vytváří při spuštění Twinkle. &Hide in system tray when closing main window Skrýt v systémové liště při zavření &hlavního okna Alt+H Enable this option if you want Twinkle to hide in the system tray when you close the main window. Povolte, pokud chcete, aby se Twinkle při zavření hlavního okna skryl v systémové liště. Startup Start programu Next time you start Twinkle, this IP address will be automatically selected. This is only useful when your computer has multiple and static IP addresses. Zde uvedená IP adresa bude automaticky vybrána při příštím startu programu. To má smysl jen pokud má tento počítač vícero síťových připojení a jen jedno je s přístupem do internetu. Default &IP address: Standardní &IP adresa: Next time you start Twinkle, the IP address of this network interface be automatically selected. This is only useful when your computer has multiple network devices. Pokud má tento počítač vícero síťových připojení, je zde možné uvést, které má být zvoleno. Při příštím startu programu již na toto nebude dotazováno. Default &network interface: Standardní síťové &rozhraní: S&tartup hidden in system tray Spustit zminimalizované do &systémové lišty Next time you start Twinkle it will immediately hide in the system tray. This works best when you also select a default user profile. Při příštím startu se Twinkle ihned ukryje v systémové liště. Pro nejlepší výsledky také zvolte výchozí profil. Default user profiles Standardní uživatelský profil If you always use the same profile(s), then you can mark these profiles as default here. The next time you start Twinkle, you will not be asked to select which profiles to run. The default profiles will automatically run. Pokud vždy používáte ty samé profily, pak je zde můžete označit jako výchozí. Při příštím spuštění Twinkle nebudete na výběr profilů dotazováni. Automaticky budou spuštěny výchozí profily. Services Služby Call &waiting Čekající hovor&y Alt+W Alt+Y With call waiting an incoming call is accepted when only one line is busy. When you disable call waiting an incoming call will be rejected when one line is busy. S funkcí čekajících hovorů jsou příchozí hovory přijímány, jen pokud je využita jen jedna linka. Pokud tuto funkci zakážete, pak budou odmítány hovory, i když je používána jen jediná linka. Hang up &both lines when ending a 3-way conference call. Zavěsit o&bě linky při ukončování konferenčního hovoru. Alt+B Hang up both lines when you press bye to end a 3-way conference call. When this option is disabled, only the active line will be hung up and you can continue talking with the party on the other line. Pokud je aktivováno budou při "zavěšení" zavěšeny obě linky. Jinak dojde jen k ukončení volání na aktivní lince a je možné pokračovat v hovoru na druhé lince. &Maximum calls in call history: &Maximální počet záznamů v seznamu volání: The maximum number of calls that will be kept in the call history. Délka seznamu volání bude omezena na zde zadaný počet záznamů. Starší záznamy budou automaticky odstraněny. &Auto show main window on incoming call after Při hovoru zobrazit &automaticky hlavní okno Alt+A Alt+A When the main window is hidden, it will be automatically shown on an incoming call after the number of specified seconds. Pokud je hlavní okno programu skryto, bude při příchozím hovoru po zadaném počtu sekund automaticky zobrazeno. Number of seconds after which the main window should be shown. Čas v sekundách, po kterém bude hlavní okno programu zobrazeno. secs sekund The UDP port used for sending and receiving SIP messages. UDP Port pro SIP Protokoll. Standardně je to 5060. Nicméně váš VoIP provider může vyžadovat jiný port. &RTP port: &RTP port: The UDP port used for sending and receiving RTP for the first line. The UDP port for the second line is 2 higher. E.g. if port 8000 is used for the first line, then the second line uses port 8002. When you use call transfer then the next even port (eg. 8004) is also used. První port přes který běží datový přenos hovoru. Současně vedený hovor na druhé lince používá port o 2 čísla vyšší. Zprostředkování hovoru potom další 2 porty. Např. 1. linka: 8000(+8001), 2. linka: 8002(+8003), Zprostředkování: 8004(+8005). Standardně je to většinou 8000 nebo 5004. Je to však závislé od konkrétního poskytovatele VoIP připojení. Při větším množství SIP telefonů připojených na jedno internetové připojení, potřebuje každý vyhrazenou vlastní skupinu portů! Tedy druhý telefon např. 8006 a výše. &SIP UDP port: &SIP UDP port: Ring tone Vyzváněcí tón &Play ring tone on incoming call Při příchozím volání &přehrávat vyzváněcí tón Alt+P Indicates if a ring tone should be played when a call comes in. Indikuje, zda má při příchozích hovorech znít vyzvánění. &Default ring tone &Výchozí vyzváněcí tón Play the default ring tone when a call comes in. Spustí výchozí vyzváněcí tón při příchozím volání. C&ustom ring tone Individ&uální vyzváněcí tón Alt+U Play a custom ring tone when a call comes in. Při příchozím volání přehrávat vlastní vyzváněcí tón. Specify the file name of a .wav file that you want to be played as ring tone. Zadejte název .wav souboru s vlastním vyzváněcím tónem. Ring back tone Tón pro signalizaci vyzvánění u volaného P&lay ring back tone when network does not play ring back tone Přehrát tón pro vyzvánění u vo&laného, pokud telefonní síť žádný tón neposkytuje Alt+L <p> Play ring back tone while you are waiting for the far-end to answer your call. </p> <p> Depending on your SIP provider the network might provide ring back tone or an announcement. </p> <p>Přehrát tón pro vyzvánění u volaného, pokud telefonní síť žádný takový tón neposkytuje.</p> <p>Přítomnost tohoto tónu závisí na vašem poskytovateli.</p> D&efault ring back tone Výchozí tón pro vyzvánění u volan&ého Play the default ring back tone. Přehrávat výchozí tón vyzvánění u volaného. Cu&stom ring back tone Vla&stní tón vyzvánění u volaného Play a custom ring back tone. Použít vlastní vyzváněcí tón u volaného. Specify the file name of a .wav file that you want to be played as ring back tone. Zadat jméno .wav souboru pro váš vlastní tón vyzvánění u volaného. &Lookup name for incoming call Podle čísla &zjistit jméno volajícího Ove&rride received display name P&řepisovat jméno volajícího Alt+R The caller may have provided a display name already. Tick this box if you want to override that name with the name you have in your address book. Volající protistrana může posílat vlastní jméno. Při aktivaci této volby bude zobrazované jméno vzato z vašeho lokálního adresáře. Lookup &photo for incoming call Hledat &fotografii volajícího Lookup the photo of a caller in your address book and display it on an incoming call. Hledat fotografii ve vašem lokálním adresáři a zobrazit při příchozím volání. &OK Alt+O Accept and save your changes. Přijmout změny a uložit. &Cancel &Zrušit Alt+C Alt+Z Undo all your changes and close the window. Vrátit zpět všechny změny a zavřít okno. none This is the 'none' in default IP address combo auto none This is the 'none' in default network interface combo auto Either choose a default IP address or a default network interface. Vybrat buď standardní IP adresu nebo standardní síťové rozhraní. Ring tones Description of .wav files in file dialog Vyzváněcí tóny Choose ring tone Vybrat vyzváněcí tón Ring back tones Description of .wav files in file dialog Tón vyzvánění u volaného Choose ring back tone Vybrat tón vyzvánění u volaného &Validate devices before usage Ověřit nasta&vení zvuku před použitím Alt+V Alt+V <p> Twinkle validates the audio devices before usage to avoid an established call without an audio channel. <p> On startup of Twinkle a warning is given if an audio device is inaccessible. <p> If before making a call, the microphone or speaker appears to be invalid, a warning is given and no call can be made. <p> If before answering a call, the microphone or speaker appears to be invalid, a warning is given and the call will not be answered. <p>Twinkle ověřuje zvuková zařízení před použitím, aby se nestalo, že při hovoru nebude fungovat zvuk. <p>Pokud je aktivováno, Twinkle při startu zkontroluje zdali je zadané audio zařízení přístupné.</p> <p>Pokud se zdá, že mikrofon nebo reproduktory/sluchátko nejsou v pořádku, bude zobrazeno varovné hlášení a žádné volání nebude dovoleno.</p> <p>Rovněž v případě, že je detekováno příchozí volání a audio zařízení není v pořádku, zobrazí se varování a hovor nebude možné přijmout. On an incoming call, Twinkle will try to find the name belonging to the incoming SIP address in your address book. This name will be displayed. Při příchozím volání se Twinkle bude pokoušet najít k volajícímu v lokálním adresáři odpovídající záznam. Pokud se to podaří, bude jeho jméno zobrazeno. Select ring tone file. Vybrat soubor s vyzváněcím tónem. Select ring back tone file. Vybrat soubor vyzváněcího tónu u protistrany. Maximum allowed size (0-65535) in bytes of an incoming SIP message over UDP. Maximálně povolená velikost příchozí SIP zprávy přes UDP v bajtech (0-65535). &SIP port: &SIP port: Max. SIP message size (&TCP): Max. velikost SIP zprávy (&TCP): The UDP/TCP port used for sending and receiving SIP messages. UDP/TCP port použitý pro odesílání a přijímání SIP zpráv. Max. SIP message size (&UDP): Max. velikost SIP zprávy (&UDP): Maximum allowed size (0-4294967295) in bytes of an incoming SIP message over TCP. Maximálně povolená velikost příchozí SIP zprávy přes TCP v bajtech (0-4294967295). W&eb browser command: Příkaz pro webový prohlížeč: Command to start your web browser. If you leave this field empty Twinkle will try to figure out your default web browser. Příkaz pro spuštění webového prohlížeče. Pokud ponecháte toto pole prázdné, Twinkle se pokusí zjistit váš výchozí prohlížeč. 512 512 1024 1024 Tip: for crackling sound with PulseAudio, set play period size to maximum. Tip: při praskajícím zvuku s PulseAudio nastavte nejvyšší periodu pro přehrávání ALSA. Enable in-call OSD Povolit OSD během hovoru SysTrayPopup Answer Přijmout Reject Odmítnout Incoming Call Příchozí hovor TermCapForm Twinkle - Terminal Capabilities Twinkle - Parametry protistrany &From: &Od: Get terminal capabilities of Dotázat se na parametry protistrany &To: &Adresa: The address that you want to query for capabilities (OPTION request). This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Adresa nebo číslo protistrany jejíž parametry se mají zjistit (OPTION request). Může to být úplná SIP adresa jako <b>sip:example@example.com</b> nebo jen uživatel nebo telefonní číslo z úplné adresy. Pokud není adresa kompletní, Twinkle doplní adresu o doménu z vašeho profilu. Address book Adresář Select an address from the address book. Výběr adresy z adresáře. &OK &Cancel &Zrušit F10 F10 TransferForm Twinkle - Transfer Twinkle - Přepojení Transfer call to Přepojit hovor na &To: &Na: The address of the person you want to transfer the call to. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Adresa nebo číslo protistrany, na kterou má být hovor přepojen. Může to být úplná SIP adresa jako <b>sip:example@example.com</b> nebo jen uživatel nebo telefonní číslo z úplné adresy. Pokud není zadána plná SIP adresa, Twinkle doplní adresu o doménu z uživatelského profilu. Address book Adresář Select an address from the address book. Výběr adresy z adresáře. &OK Alt+O &Cancel &Zrušit Type of transfer Typ přesměrování &Blind transfer &Slepé přesměrování Alt+B Transfer the call to a third party without contacting that third party yourself. Hovor přímo přesměrovat na nového účastníka, aniž by byl nejdřív kontaktován. T&ransfer with consultation Asistované p&řepojení Alt+R Alt+R Before transferring the call to a third party, first consult the party yourself. Před přesměrováním hovoru nejprve kontaktovat nového účastníka a volajícího ohlásit. Transfer to other &line Přesměrovat na jinou &linku Alt+L Connect the remote party on the active line with the remote party on the other line. Přepojit hovor na aktivní lince s hovorem na druhé lince. F10 F10 TwinkleCore Failed to create log file %1 . Chyba při vytvoření logového souboru "%1". Cannot open file for reading: %1 Nelze otevřít soubor %1 ke čtení File system error while reading file %1 . Chyba systému souborů při čtení souboru %1. Cannot open file for writing: %1 Nelze otevřít soubor %1 pro zápis File system error while writing file %1 . Chyba systému souborů při zápisu do %1. Excessive number of socket errors. Příliš velký počet socketových chyb. Built with support for: Vytvořeno s podporou pro: Contributions: Pomocní vývojáři: This software contains the following software from 3rd parties: Tento program obsahuje softwarové části těchto třetích stran: * GSM codec from Jutta Degener and Carsten Bormann, University of Berlin * GSM kodek od Jutta Degener a Carsten Bormann, University of Berlin * G.711/G.726 codecs from Sun Microsystems (public domain) * G.711/G.726 kodeky od Sun Microsystems (public domain) * iLBC implementation from RFC 3951 (www.ilbcfreeware.org) * iLBC implementace RFC 3951 (www.ilbcfreeware.org) * Parts of the STUN project at http://sourceforge.net/projects/stun * Části ze STUN projektu na http://sourceforge.net/projects/stun * Parts of libsrv at http://libsrv.sourceforge.net/ * Části z libsrv na http://libsrv.sourceforge.net/ For RTP the following dynamic libraries are linked: Pro RTP jsou linkovány následující dynamické knihovny: Translated to english by <your name> Do češtiny přeložil Luboš Doležel Directory %1 does not exist. Adresář "%1" neexistuje. Cannot open file %1 . Soubor %1 nelze otevřít. %1 is not set to your home directory. "%1" nní vaším domovským adresářem. Directory %1 (%2) does not exist. Adresář %1 (%2) neexistuje. Cannot create directory %1 . Adresář "%1" nelze vytvořit. Lock file %1 already exist, but cannot be opened. Zamykací soubor "%1" již existuje, ale nemůže být otevřen. %1 is already running. Lock file %2 already exists. %1 již běží. Zamykací soubor "%2" již existuje. Cannot create %1 . Nelze vytvořit "%1" . Cannot write to %1 . Nelze zapisovat do "%1". Syntax error in file %1 . Syntaktická chyba v souboru "%1" . Failed to backup %1 to %2 Chyba při zálohování %1 do %2 unknown name (device is busy) neznámý název (zařízení je zaneprázdněné) Default device Výchozí zařízení Anonymous Anonymní Warning: Varování: Call transfer - %1 Přesměrování hovoru - %1 Sound card cannot be set to full duplex. Zvuková karta nefunguje v režimu full duplex. Cannot set buffer size on sound card. Nelze nastavit velikost bufferu na zvukové kartě. Sound card cannot be set to %1 channels. Zvukové zařízení neze nastavit na %1 kanálů. Cannot set sound card to 16 bits recording. Audio zařízení nelze nastavit na 16 bitové nahrávání. Cannot set sound card to 16 bits playing. Audio zařízení nelze nastavit na 16 bitové přehrávání. Cannot set sound card sample rate to %1 Nelze nastavit vzorkovací frekvenci audio zařízení na %1 Opening ALSA driver failed Chyba při otevírání ovladače ALSA Cannot open ALSA driver for PCM playback Nelze otevřít ovladač ALSA pro přehrávání PCM Cannot resolve STUN server: %1 Nelze vyhledat adresu STUN serveru: %1 You are behind a symmetric NAT. STUN will not work. Configure a public IP address in the user profile and create the following static bindings (UDP) in your NAT. Nacházíte se za symetrickým NATem. STUN nebude fungovat. Je nutné nastavit v uživatelském profilu veřejnou IP adresu a na vašem NATu namapovat (UDP) porty. public IP: %1 --> private IP: %2 (SIP signaling) veřejná IP: %1 --> privátní IP: %2 (signalizace SIP) public IP: %1-%2 --> private IP: %3-%4 (RTP/RTCP) veřejná IP: %1 - %2 --> privátní IP: %3 - %4 (RTP/RTCP) Cannot reach the STUN server: %1 Nelze se připojit na STUN server: %1 Port %1 (SIP signaling) Port %1 (signalizace SIP) NAT type discovery via STUN failed. Detekce typu NATu pomocí STUN selhala. If you are behind a firewall then you need to open the following UDP ports. Pokud se nacházíte za firewallem, je nutné otevřít následující UDP porty. Ports %1-%2 (RTP/RTCP) Porty %1-%2 (RTP/RTCP) Cannot access the ring tone device (%1). Zvukové zařízení "%1" pro vyzváněcí tón není přístupné. Cannot access the speaker (%1). Zvukové zařízení "%1" pro reproduktory/sluchátka není přístupné. Cannot access the microphone (%1). Zvukové zařízení %1 pro mikrofon není přístupné. Cannot open ALSA driver for PCM capture Nelze otevřít ovladač ALSA pro nahrávání PCM Cannot receive incoming TCP connections. Nelze přijímat příchozí TCP spojení. Failed to create file %1 Selhalo vytvoření souboru %1 Failed to write data to file %1 Selhal zápis dat do souboru %1 Failed to send message. Selhalo odeslání zprávy. Cannot lock %1 . Nemohu uzamknout %1. UserProfileForm Twinkle - User Profile Twinkle - Uživatelský profil User profile: Uživatelský profil: Select which profile you want to edit. Vyberte profil, který chcete upravit. User Uživatel SIP server SIP server RTP audio RTP audio SIP protocol Protokol SIP NAT NAT (překlad adres) Address format Formát adresy Timers Časovače Ring tones Vyzváněcí tóny Scripts Skripty Security Zabezpečení Select a category for which you want to see or modify the settings. Vybrat oblast, ve které mají být provedeny změny nastavení. &OK Alt+O Accept and save your changes. Akceptovat a uložit změny v nastavení. &Cancel &Zrušit Alt+C Alt+Z Undo all your changes and close the window. Vrátit zpět všechny změny a zavřít okno. SIP account Účet SIP &User name*: Uživatelské &jméno*: &Domain*: &Doména*: Or&ganization: Or&ganizace: The SIP user name given to you by your provider. It is the user part in your SIP address, <b>username</b>@domain.com This could be a telephone number. <br><br> This field is mandatory. Uživatelské jméno, které vám bylo přiděleno vaším poskytovatelem,.je první částí vaší úplné SIP adresy <b>uzivatel</b>@domain.com Může se jednat o telefonní číslo. <br><br> Toto pole je povinné. The domain part of your SIP address, username@<b>domain.com</b>. Instead of a real domain this could also be the hostname or IP address of your <b>SIP proxy</b>. If you want direct IP phone to IP phone communications then you fill in the hostname or IP address of your computer. <br><br> This field is mandatory. Doménová část vaší adresy SIP, uzivatel@<b>domain.com</b>. Místo skutečné domény zde může být také název hostitele nebo IP adresa vaší <b>SIP proxy</b> Pro přímé volání mezi IP adresami se zde uvede název hostitele nebo IP, pod kterým je váš počítač dosažitelný v internetu. <br><br> Toto pole je povinné. You may fill in the name of your organization. When you make a call, this might be shown to the called party. Zde je možné uvést jméno vaší organizace. Pokud někomu voláte, může být tento údaj zobrazen protistraně. This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. Vaše jméno nebo přezdívka. Tato položka je volané protistraně zobrazována jako jméno volajícího. &Your name: Vaše &jméno: SIP authentication Přihlašovací údaje SIP &Realm: &Realm: Authentication &name: Přihlašovací &jméno: &Password: &Heslo: The realm for authentication. This value must be provided by your SIP provider. If you leave this field empty, then Twinkle will try the user name and password for any realm that it will be challenged with. Hodnota "realm" pro přihlášení. Tento údaj vám musí poskytnout váš poskytovatel SIP. Pokud zůstane pole prázdné, pak se Twinkle pokusí o přihlášení s vaším uživatelským jménem a heslem při libovolném realmu. Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Vaše přihlašovací SIP jméno. Je často identické s vaším uživatelským SIP jménem. Může se ale také lišit. Your password for authentication. Vaše přihlašovací heslo. Registrar Registrar (přihlašovací server) &Registrar: &Registrar: The hostname, domain name or IP address of your registrar. If you use an outbound proxy that is the same as your registrar, then you may leave this field empty and only fill in the address of the outbound proxy. Název hostitele, doménové jméno nebo IP adresa přihlašovacího serveru. Pokud používáte odchozí proxy, která je stejná jako váš přihlašovací server, pak je možné toto pole ponechat prázdné a vyplnit jen adresu odchozí proxy. &Expiry: &Platnost: The registration expiry time that Twinkle will request. Doba platnosti přihlášení v sekundách, kterou si Twinkle vyžádá. seconds sekund Re&gister at startup Při&hlásit při spuštění Alt+G Alt+H Indicates if Twinkle should automatically register when you run this user profile. You should disable this when you want to do direct IP phone to IP phone communication without a SIP proxy. Značí, zda se má Twinkle automaticky přihlásit při spuštění tohoto profilu. Toto byste měli zakázat, pokud chcete přímé volání mezi IP adresami bez SIP proxy. Outbound Proxy Odchozí proxy &Use outbound proxy Po&užít odchozí-proxy Alt+U Alt+U Indicates if Twinkle should use an outbound proxy. If an outbound proxy is used then all SIP requests are sent to this proxy. Without an outbound proxy, Twinkle will try to resolve the SIP address that you type for a call invitation for example to an IP address and send the SIP request there. Značí, zda má Twinkle používat odchozí proxy. Pokud se používá odchozí proxy, pak jsou všechny požadavky SIP posílány na tuto proxy. Bez odchozí proxy se Twinkle pokusí použít název hostitele z adresy SIP a směrovat hovor přímo tam. Outbound &proxy: Odchozí &proxy: &Send in-dialog requests to proxy Poslat in-&dialog dotazy na proxy Alt+S Alt+D SIP requests within a SIP dialog are normally sent to the address in the contact-headers exchanged during call setup. If you tick this box, that address is ignored and in-dialog request are also sent to the outbound proxy. Požadavky SIP se běžně posílají na adresu v kontaktních hlavičkách vyměněných při sestavování hovoru. Pokud je toto pole zaškrtnuté, pak je tato adresa ignorována a všechny žádosti se rovněž zasílají na odchozí proxy. &Don't send a request to proxy if its destination can be resolved locally. Neposílat žá&dosti SIP na proxy, pokud lze cíl spojit lokálně. Alt+D Alt+D When you tick this option Twinkle will first try to resolve a SIP address to an IP address itself. If it can, then the SIP request will be sent there. Only when it cannot resolve the address, it will send the SIP request to the proxy (note that an in-dialog request will only be sent to the proxy in this case when you also ticked the previous option.) Pokud je aktivováno, pokusí se nejprve Twinkle najít k cílové adrese odpovídající IP adresu a poslat SIP dotaz přímo tam. Pokud se nepodaří IP adresu zjistit, je dotaz poslán na proxy. (Upozornění:in-dialog žádosti budou v tomto případě posílány na proxy, jen pokud je aktivována i předchozí volba.) The hostname, domain name or IP address of your outbound proxy. Doménové jméno, IP adresa nebo jméno vaší odchozí proxy. Co&decs Ko&deky Codecs Kodeky Available codecs: Dostupné kodeky: G.711 A-law G.711 u-law GSM speex-nb (8 kHz) speex-wb (16 kHz) speex-uwb (32 kHz) List of available codecs. Seznam dostupných, kodeků. Move a codec from the list of available codecs to the list of active codecs. Přesunout kodek ze seznamu dostupných kodeků do seznamu aktivních kodeků. Move a codec from the list of active codecs to the list of available codecs. Deaktivovat kodek. Active codecs: Aktivní kodeky: List of active codecs. These are the codecs that will be used for media negotiation during call setup. The order of the codecs is the order of preference of use. Seznam aktivních kodeků. Tyto budou použity při navázání spojení s protistranou. Pořadí zde uvedených kodeků je zároveň pořadím v jakém budou kodeky upřednostňovány. Move a codec upwards in the list of active codecs, i.e. increase its preference of use. Přesunout tento kodek směrem nahoru v seznamu kodeků, tzn. zvýšit jeho prioritu. Move a codec downwards in the list of active codecs, i.e. decrease its preference of use. Přesunout tento kodek směrem dolů v seznamu kodeků, tzn. snížit jeho prioritu. &G.711/G.726 payload size: &G.711/G.726 velikost payloadu: The preferred payload size for the G.711 and G.726 codecs. Upřednostňovaná velikost payloadu pro kodeky G.711 a G.726. ms &iLBC iLBC i&LBC payload type: i&LBC typ payloadu: iLBC &payload size (ms): iLBC velikost &payloadu (ms): The dynamic type value (96 or higher) to be used for iLBC. Dynamická hodnota typu pro iLBC (96 nebo více). 20 30 The preferred payload size for iLBC. Upřednostňovaná velikost payloadu pro iLBC. &Speex Speex Perceptual &enhancement Vylepšení &kvality zvuku Alt+E Perceptual enhancement is a part of the decoder which, when turned on, tries to reduce (the perception of) the noise produced by the coding/decoding process. In most cases, perceptual enhancement make the sound further from the original objectively (if you use SNR), but in the end it still sounds better (subjective improvement). Vylepšení vnímané kvality zvuku je součástí dekodéru, která se snaží snížit (vnímaný) šum vzniklý při procesu kódování/dekódování. Ve většině případů vede tato funkce k většímu objektivnímu odchýlení zvuku od originálu (z hlediska SNR), ale zní přesto lépe (subjektivní vylepšení). &Ultra wide band payload type: Typ payload&u pro ultra široké pásmo: Alt+V When enabled, voice activity detection detects whether the audio being encoded is speech or silence/background noise. VAD is always implicitly activated when encoding in VBR, so the option is only useful in non-VBR operation. In this case, Speex detects non-speech periods and encode them with just enough bits to reproduce the background noise. This is called "comfort noise generation" (CNG). Pokud je aktivováno, testuje systém VAD (Voice Activity Detection), jestli je právě mluveno nebo je v hovoru pauza. Zvuky které nejsou rozpoznány jako hovor jsou nahrazeny jen několika málo bity napodobující šum pozadí. To vede k redukci přenášených dat. Systém VAD je vždy aktivován, pokud je nastaveno kódováni s VBR. &Wide band payload type: Typ payloadu pro široké pásm&o: Alt+B Variable bit-rate (VBR) allows a codec to change its bit-rate dynamically to adapt to the "difficulty" of the audio being encoded. In the example of Speex, sounds like vowels and high-energy transients require a higher bit-rate to achieve good quality, while fricatives (e.g. s,f sounds) can be coded adequately with less bits. For this reason, VBR can achieve a lower bit-rate for the same quality, or a better quality for a certain bit-rate. Despite its advantages, VBR has two main drawbacks: first, by only specifying quality, there's no guarantee about the final average bit-rate. Second, for some real-time applications like voice over IP (VoIP), what counts is the maximum bit-rate, which must be low enough for the communication channel. Variabilní šířka pásma (VBR) umožní danému kodeku přizpůsobit množství dat potřebných k přenosu hovoru charakteru audio signálu. Zatímco např. některé ostré samohlásky nebo velmi proměnné pasáže potřebují velkou vzorkovací frekvenci a tím velký datový tok, tak měkké souhlásky vystačí s malým datovým tokem. Díky VBR tak lze při dané datové rychlosti docílit lepší kvality zvuku nebo při dané kvalitě hovoru vystačit s nižším datovým tokem. Nevýhodou je, že při zadané kvalitě nelze předpovědět jaký velký datový tok bude ve skutečnosti. A také, že v aplikacích pracujících v reálném čase (jako je právě VoIP) je rozhodující maximální šířka pásma, kterou musí zvládnout komunikační kanál. The dynamic type value (96 or higher) to be used for speex wide band. Dynamická hodnota typu pro speex wide band (96 nebo vyšší). Co&mplexity: Ko&mplexita: Discontinuous transmission is an addition to VAD/VBR operation, that allows one to stop transmitting completely when the background noise is stationary. Nesouvislé vysílání je rozšířením funkčnosi VAD/VBR, kdy je možné úplně přestat odesílat data v případě ticha. The dynamic type value (96 or higher) to be used for speex narrow band. Dynamická hodnota typu pro speex narrow band (96 nebo vyšší). With Speex, it is possible to vary the complexity allowed for the encoder. This is done by controlling how the search is performed with an integer ranging from 1 to 10 in a way that's similar to the -1 to -9 options to gzip and bzip2 compression utilities. For normal use, the noise level at complexity 1 is between 1 and 2 dB higher than at complexity 10, but the CPU requirements for complexity 10 is about 5 times higher than for complexity 1. In practice, the best trade-off is between complexity 2 and 4, though higher settings are often useful when encoding non-speech sounds like DTMF tones. V případě Speexu je možné měnit komplexitu kodéru. Slouží to k zadání hloubky hledání v rozsahu od 1 do 10. Podobný princip je zaveden v kompresních programech gzip a bzip2 s volbou -1 až -9 . Za normálních podmínek je odstup šumu při komplexitě 1 o 1 až 2dB vyšší než při komplexitě 10. Nicméně CPU vytížení je asi 5x vyšší než při komplexitě 1. V praxi se osvědčilo nastavení mezi 2 až 4. Vyšší nastavení jsou vhodná pro přenos tónů DTMF nebo hudebního signálu. &Narrow band payload type: Typ payloadu pro úz&ké pásmo: G.726 G.726 &40 kbps payload type: Typ payloadu pro G.726 &40 kb/s: The dynamic type value (96 or higher) to be used for G.726 40 kbps. Dynamická hodnota typu pro G.726 40 kb/s (96 nebo vyšší). The dynamic type value (96 or higher) to be used for G.726 32 kbps. Dynamická hodnota typu pro G.726 32 kb/s (96 nebo vyšší). G.726 &24 kbps payload type: Typ payloadu pro G.726 &24 kb/s: The dynamic type value (96 or higher) to be used for G.726 24 kbps. Dynamická hodnota typu pro G.726 24 kb/s (96 nebo vyšší). G.726 &32 kbps payload type: Typ payloadu pro G.726 &32 kb/s: The dynamic type value (96 or higher) to be used for G.726 16 kbps. Dynamická hodnota typu pro G.726 16 kb/s (96 nebo vyšší). G.726 &16 kbps payload type: Typ payloadu pro G.726 &16 kb/s: DT&MF DTMF The dynamic type value (96 or higher) to be used for DTMF events (RFC 2833). Dynamická hodnota typu pro události DTMF (RFC 2833) (96 nebo vyšší). DTMF vo&lume: H&lasitost DTMF: The power level of the DTMF tone in dB. Hlasitost vysílaných DTMF tónů v dB. The pause after a DTMF tone. Doba prodlevy mezi dvěmi DTMF tóny. DTMF &duration: Trvání &DTMF: DTMF payload &type: &Typ payloadu DTMF: DTMF &pause: &Prodleva DTMF: dB Duration of a DTMF tone. Doba trvání tónu DTMF. DTMF t&ransport: P&řenos DTMF: Auto Automaticky RFC 2833 RFC 2833 Inband Zvukově Out-of-band (SIP INFO) SIP INFO <h2>RFC 2833</h2> <p>Send DTMF tones as RFC 2833 telephone events.</p> <h2>Inband</h2> <p>Send DTMF inband.</p> <h2>Auto</h2> <p>If the far end of your call supports RFC 2833, then a DTMF tone will be send as RFC 2833 telephone event, otherwise it will be sent inband. </p> <h2>Out-of-band (SIP INFO)</h2> <p> Send DTMF out-of-band via a SIP INFO request. </p> <p><h3>RFC 2833</h3> Vysílá DTMF tóny jako telefonní události dle RFC 2833.</p> <p><h3>Zvukově</h3> Vysílá DTMF inband (skutečné tóny, které Twinkle přimíchá do audio signálu).</p> <p><h3>Auto</h3> Pokud protistrana podporuje RFC 2833, jsou použity DTMF tóny dle RFC 2833 standardu, jinak jako inband.</p> <p><h3>SIP INFO</h3> Vysílá DTMF out-of-band přes požadavek SIP INFO.</p> General Obecné Redirection Přesměrování &Allow redirection Povolit přesměrov&ání Alt+A Indicates if Twinkle should redirect a request if a 3XX response is received. Značí, zda má Twinkle přesměrovat požadavek při odpovědi 3XX. Ask user &permission to redirect Dotázat se uživatele před &přesměrováním Alt+P Alt+W Indicates if Twinkle should ask the user before redirecting a request when a 3XX response is received. Pokud je aktivováno, dotáže se Twinkle při přijmutí požadavku 3XX uživatele, zda-li má být odchozí volání přesměrováno na nový cíl. Max re&directions: Max. počet &přesměrování: The number of redirect addresses that Twinkle tries at a maximum before it gives up redirecting a request. This prevents a request from getting redirected forever. Počet přesměrování odchozího volání, po kterém Twinkle ukončí pokusy navázat spojení. Zabraňuje zacyklení při přesměrování. Protocol options Nastavení protokolu SIP Call &Hold variant: Způsob přidržení &hovoru: RFC 2543 RFC 3264 Indicates if RFC 2543 (set media IP address in SDP to 0.0.0.0) or RFC 3264 (use direction attributes in SDP) is used to put a call on-hold. Značí, zda-li bude k podržení hovoru použito RFC 2543 (nastavení IP adresy v SDP na 0.0.0.0) nebo RFC 3264 (použít směrové atributy v SDP). Allow m&issing Contact header in 200 OK on REGISTER Povolit chybějící kontaktní hlavičku v 200 OK při REG&ISTER Alt+I A 200 OK response on a REGISTER request must contain a Contact header. Some registrars however, do not include a Contact header or include a wrong Contact header. This option allows for such a deviation from the specs. Odpověď 200 OK na požadavek REGISTER musí obsahovat hlavičku Contact. Někteří poskytovatelé buď hlavičku Contact neposílají, nebo je chybná. Tato možnost povolí takové odchýlení od specifikací. &Max-Forwards header is mandatory Hlavička &Max-Forwards je vyžadována Alt+M According to RFC 3261 the Max-Forwards header is mandatory. But many implementations do not send this header. If you tick this box, Twinkle will reject a SIP request if Max-Forwards is missing. Podle RFC3261 je hlavička Max-Forwards povinná. Často však není posílána. Pokud je aktivováno, pak Twinkle odmítne SIP požadavky, které hlavičku Max-Forwards neobsahují. Put &registration expiry time in contact header Vložit do kontaktní hlavičky dobu platnosti při&hlášení Alt+R In a REGISTER message the expiry time for registration can be put in the Contact header or in the Expires header. If you tick this box it will be put in the Contact header, otherwise it goes in the Expires header. Doba vypršení platnosti přihlášení v požadavku REGISTER může být přenášena jak v hlavičce Contact, tak i v hlavičce Expires. Pokud je aktivováno, posílá ji Twinkle v hlavičce Contact, jinak v hlavičce Expires. &Use compact header names Použít &kompaktní názvy hlaviček Indicates if compact header names should be used for headers that have a compact form. Pokud je aktivováno, bude pro názvy hlaviček použita krátká forma, pokud existuje. Allow SDP change during call setup Povolit změny v SDP při navázání volání <p>A SIP UAS may send SDP in a 1XX response for early media, e.g. ringing tone. When the call is answered the SIP UAS should send the same SDP in the 200 OK response according to RFC 3261. Once SDP has been received, SDP in subsequent responses should be discarded.</p> <p>By allowing SDP to change during call setup, Twinkle will not discard SDP in subsequent responses and modify the media stream if the SDP is changed. When the SDP in a response is changed, it must have a new version number in the o= line.</p> <p>SIP UAS může odesílat SDP v 1XX odpovědi na zvuk zkraje hovoru, např. vyzváněcí tón. Pokud bude volání přijmuto, měl by SIP UAS dle RFC 3261 poslat to samé SDP v odpovědi 200 OK. Po přijmutí SDP by měly být všechny následující odpovědi SDP být zahozeny.</p> <p>Pokud je povoleno, že se SDP během navázání hovoru může změnit, Twinkle nebude v následujících odpovědích ignorovat SDP, nýbrž změní požadovaným způsobem vlastnosti proudu RTP. Změněné SDP musí mít v "o=" řádku nové číslo verze.</p> <p> Twinkle creates a unique contact header value by combining the SIP user name and domain: </p> <p> <tt>&nbsp;user_domain@local_ip</tt> </p> <p> This way 2 user profiles, having the same user name but different domain names, have unique contact addresses and hence can be activated simultaneously. </p> <p> Some proxies do not handle a contact header value like this. You can disable this option to get a contact header value like this: </p> <p> <tt>&nbsp;user@local_ip</tt> </p> <p> This format is what most SIP phones use. </p> <p>Pokud je aktivováno, vytvoří Twinkle jednoznačnou hodnotu kontaktní hlavičky pomocí kombinace uživatelského SIP jména a doménového jména: <br> <tt>&nbsp;user_domain@local_ip</tt> </p> <p> Je tím umožněno vytvoření vícero uživatelských profilů se stejným uživatelským jménem, ale rozdílnou doménou, které pak mají odlišné kontaktní adresy a proto tyto profily lze použít současně. </p> <p> Některé proxy nemusí s takovýmito kontaktními hlavičkami umět zacházet. Pokud je tato volba deaktivována, posílá Twinkle kontaktní hlavičku v následujícím formátu: <br> <tt>&nbsp;user@local_ip</tt> </p> <p> Tento formát je používán většinou SIP telefonů. </p> &Encode Via, Route, Record-Route as list Via, Route a Record-Route poslat jako &seznam The Via, Route and Record-Route headers can be encoded as a list of comma separated values or as multiple occurrences of the same header. Via-, Route- und Record-Route-Header mohou posílány zakódované jako seznam čárkou oddělených hodnot nebo jako jednotlivé hodnoty, každá ve své hlavičce. SIP extensions Rozšíření SIP &100 rel (PRACK): disabled deaktivováno supported podporováno required vyžadováno preferred upřednostňováno Indicates if the 100rel extension (PRACK) is supported:<br><br> <b>disabled</b>: 100rel extension is disabled <br><br> <b>supported</b>: 100rel is supported (it is added in the supported header of an outgoing INVITE). A far-end can now require a PRACK on a 1xx response. <br><br> <b>required</b>: 100rel is required (it is put in the require header of an outgoing INVITE). If an incoming INVITE indicates that it supports 100rel, then Twinkle will require a PRACK when sending a 1xx response. A call will fail when the far-end does not support 100rel. <br><br> <b>preferred</b>: Similar to required, but if a call fails because the far-end indicates it does not support 100rel (420 response) then the call will be re-attempted without the 100rel requirement. Definuje způsob podpory rozšíření 100rel(PRACK):<br><br> <b>deaktivováno</b>: rozšíření 100rel není podporováno <br><br> <b>povoleno</b>: 100rel je podporováno (je přidáno do odchozího INVITE jako podporovaná hlavička). Protistrana si potom může vyžádat PRACK na 1xx odpověď. <br><br> <b>vyžadováno</b>: 100rel je vyžadováno. Požadavek je vložen do hlavičky require v odchozím INVITE. Pokud je v příchotím INVITE značeno, že je 100rel podporováno, pak bude Twinkle vyžadovat PRACK v odpovědi 1xx Pokud protistrana 100rel nepodporuje, nedojde k navázání spojení. <br><br> <b>upřednostňováno</b>: Podobné jako "vyžadováno", akorát že v případě, že hovor selže, jelikož protistrana nepodporuje 100rel (odpověď 420), pak se Twinkle pokusí hovor znovu navázat bez vyžadování 100rel. REFER Call transfer (REFER) Přesměrování volání (REFER) Allow call &transfer (incoming REFER) Povolit protistraně přesměrování &volání (příchozí REFER) Alt+T Indicates if Twinkle should transfer a call if a REFER request is received. Pokud je aktivováno, Twinkle následuje požadavek protistrany (REFER) přesměrovat volání na jinou adresu. As&k user permission to transfer Dotázat se uživatele na &povolení přesměrování Alt+K Alt+V Indicates if Twinkle should ask the user before transferring a call when a REFER request is received. Pokud je aktivováno, dotazuje se Twinkle při příchozím (REFER) požadavku na povolení přesměrování. Hold call &with referrer while setting up call to transfer target Podržet ho&vor, zatím co se spojuje cíl přepojení Alt+W Alt+V Indicates if Twinkle should put the current call on hold when a REFER request to transfer a call is received. Pokud je aktivováno, Twinkle při příchozím požadavku REFER na přesměrování podrží stávající hovor. Ho&ld call with referee before sending REFER Podržet hovor před odes&láním REFER Alt+L Indicates if Twinkle should put the current call on hold when you transfer a call. Značí, zda má Twinkle přidržet hovor při jeho přesměrování. Auto re&fresh subscription to refer event while call transfer is not finished Automaticky obnovovat registraci &pro REFER signál, dokud není přesměrování dokončeno Alt+F While a call is being transferred, the referee sends NOTIFY messages to the referrer about the progress of the transfer. These messages are only sent for a short interval which length is determined by the referee. If you tick this box, the referrer will automatically send a SUBSCRIBE to lengthen this interval if it is about to expire and the transfer has not yet been completed. Během přesměrování hovoru posílá zprostředkovatel přesměrování zprávy NOTIFY o postupu přesměrování na toho, jehož volání je přesměrováváno. Ovšem jen po krátkou dobu. Tu určí ten, kdo je přesměrováván. Pokud je tato volba aktivována, posílá zprostředkovatel (Twinkle) automaticky SUBCRIBEs tak, aby došlo k prodloužení tohoto času, dokud není proces přesměrování ukončen. NAT traversal Překonání NATu &NAT traversal not needed Překonání &NATu není nutné Alt+N Choose this option when there is no NAT device between you and your SIP proxy or when your SIP provider offers hosted NAT traversal. Zvolte tuto volbu, pokud se mezi Twinkle a vaší SIP proxy nenachází žádný NAT nebo pokud váš poskytovatel umí sám NAT překonat. &Use statically configured public IP address inside SIP messages Ve zprávách SIP použít &pevně nastavenou veřejnou IP adresu Indicates if Twinkle should use the public IP address specified in the next field inside SIP message, i.e. in SIP headers and SDP body instead of the IP address of your network interface.<br><br> When you choose this option you have to create static address mappings in your NAT device as well. You have to map the RTP ports on the public IP address to the same ports on the private IP address of your PC. Pokud je aktivováno, Twinkle použije uvnitř zpráv SIP (v hlavičce SIP a těle SDP) veřejnou IP adresu, namísto IP adresy vašeho síťového rozhraní.<br><br> Pokud si tuto volbu vyberete, musíte rovněž na vašem NAT zařízení nasměrovat odpovídající RTP porty na váš počítač. Use &STUN Použít &STUN Choose this option when your SIP provider offers a STUN server for NAT traversal. Vyberte tuto volbu, pokud váš poskytovatel SIP nabízí STUN server k přemostění vašeho NATu. S&TUN server: Adresa S&TUN serveru: The hostname, domain name or IP address of the STUN server. Doménové jméno, IP adresa nebo jméno STUN serveru. &Public IP address: Veřejná IP &adresa: The public IP address of your NAT. Veřejná adresa vašeho NATu. Telephone numbers Telefonní čísla Only &display user part of URI for telephone number U telefonních čísel zobrazit jen uživatelskou část &URI If a URI indicates a telephone number, then only display the user part. E.g. if a call comes in from sip:123456@twinklephone.com then display only "123456" to the user. A URI indicates a telephone number if it contains the "user=phone" parameter or when it has a numerical user part and you ticked the next option. Pokud URI značí telefonní číslo, zobrazí se jen uživatelská část adresy. Např. pokud přijde volání od sip:12345@voipprovider.com, ukáže Twinkle jako adresu jen "12345". URI je považováno za telefonní číslo, pokud obsahuje parametr "user=phone" nebo pokud je aktivní následující volba a uživatelská část adresy je numerická. &URI with numerical user part is a telephone number &URI s numerickou uživatelskou částí je telefonní číslo If you tick this option, then Twinkle considers a SIP address that has a user part that consists of digits, *, #, + and special symbols only as a telephone number. In an outgoing message, Twinkle will add the "user=phone" parameter to such a URI. Pokud je aktivováno, považuje Twinkle každou SIP adresu za telefonní číslo, pokud má v uživatelské části pouze číslice, *, #, + a zvláštní znaky. V odchozím SIP zprávách označí Twinkle takové adresy parametrem "user=phone". &Remove special symbols from numerical dial strings &Odstranit z telefonního čísla zvláštní znaky Telephone numbers are often written with special symbols like dashes and brackets to make them readable to humans. When you dial such a number the special symbols must not be dialed. To allow you to simply copy/paste such a number into Twinkle, Twinkle can remove these symbols when you hit the dial button. Aby byla telefonní čísla snázeji čitelná, bývají často zadána s použitím zvláštních znaků jako např. "(", ")", " "(prázdný znak), "-". Při vytáčení, obzvláště nějaké SIP adresy, nesmí být tyto znaky vysílány. Aby bylo možné zjednodušit vytáčení pomocí copy/paste, Twinkle může tyto znaky při vytáčení automaticky odstranit. &Special symbols: &Zvláštní znaky: The special symbols that may be part of a telephone number for nice formatting, but must be removed when dialing. Seznam všech zvláštních znaků, které má Twinkle z vytáčených čísel odstranit. Number conversion Převod čísel Match expression Vyhledávaný výraz Replace Nahradit <p> Often the format of the telphone numbers you need to dial is different from the format of the telephone numbers stored in your address book, e.g. your numbers start with a +-symbol followed by a country code, but your provider expects '00' instead of the '+', or you are at the office and all your numbers need to be prefixed with a '9' to access an outside line. Here you can specify number format conversion using Perl style regular expressions and format strings. </p> <p> For each number you dial, Twinkle will try to find a match in the list of match expressions. For the first match it finds, the number will be replaced with the format string. If no match is found, the number stays unchanged. </p> <p> The number conversion rules are also applied to incoming calls, so the numbers are displayed in the format you want. </p> <h3>Example 1</h3> <p> Assume your country code is 31 and you have stored all numbers in your address book in full international number format, e.g. +318712345678. For dialling numbers in your own country you want to strip of the '+31' and replace it by a '0'. For dialling numbers abroad you just want to replace the '+' by '00'. </p> <p> The following rules will do the trick: </p> <blockquote> <tt> Match expression = \+31([0-9]*) , Replace = 0$1<br> Match expression = \+([0-9]*) , Replace = 00$1</br> </tt> </blockquote> <h3>Example 2</h3> <p> You are at work and all telephone numbers starting with a 0 should be prefixed with a 9 for an outside line. </p> <blockquote> <tt> Match expression = 0[0-9]* , Replace = 9$&<br> </tt> </blockquote> <p> Často není formát telefonních čísel, které jsou očekávány od VoIP poskytovatele, shodný s formátem čísel uložených v adresáři. Např. u čísel začínajících na "+" a národním kódem země očekává váš poskytovatel namísto "00" znak "+". Nebo jste-li napojeni na místní SIP síť a je nutné předtočit nejdříve číslo k přístupu ven. Zde je možné za použití vyhledávacích a zaměňovacích vzorů (podle způsobu regulárních výrazů a la Perl) nastavit obecně platné pravidla pro konverzi telefonních čísel. </p> <p> Při každém vytáčení se Twinkle pokusí najít pro čísla z uživatelské části SIP adresy odpovídají výraz v seznamu hledaných vzorů. S prvním nalezeným vyhovujícím výrazem je provedena úprava originálního čísla, přičemž pozice v "(" ")" v hledaném výrazu (např. "([0-9]*)" pro "jakkoliv mnoho čísel") je nahrazena znaky v odpovídajících proměnných. Např. "$1" pro první pozici. Viz též `man 7 regex` nebo konqueror:"#regex". Pokud není nalezen žádný odpovídající hledaný vzor, zůstane číslo nezměněno. </p> <p> Pravidla budou rovněž použita na čísla v příchozích voláních. Podle nastavených pravidel budou tato přetransformována do žádaného formátu. </p> <h3>1. příklad</h3> <p> Např. váš národní kód je "420" pro českou republiku a ve vašem adresáři máte také mnoho vnitrostátních čísel uložených v mezinárodním formátu. Např.. +420 577 2345678. Avšak VoIP poskytovatel očekává pro vnitrostátní hovor 0577 2345678. Chcete tedy nahradit '+420' za '0' a zároveň pro zahraniční hovory nahradit '+' za '00'. </p> <p> K tomu jsou potřebné následující pravidla uvedená v tomto pořadí: </p> <blockquote> <tt> Hledaný výraz = \+49([0-9]*) , Náhrada = 0$1<br> Hledaný výraz = \+([0-9]*) , Náhrada = 00$1</br> </tt> </blockquote> <h3>2. příklad</h3> <p> Nacházíte se na telefonní ústředně a všem číslům s 0 jako první číslicí, má být předřazeno číslo 9. </p> <blockquote> <tt> Hledaný výraz = 0[0-9]* , Náhrada = 9$&<br> </tt> </blockquote> ( $& je speciální proměnná, do které je uloženo celé originální číslo)<br> Poznámka: Toto pravidlo nelze nastavit jednoduše jako třetí pravidlo ke dvou pravidlů z předcházejícího příkladu. Bude totiž použito vždy jen to první, které vyhledávání vyhoví. Namísto toho by muselo být změněno nahrazování v pravidlech 1 a 2 - "57$1" a "577$1" Move the selected number conversion rule upwards in the list. Posunout vybrané pravidlo konverze na vyšší pozici. Move the selected number conversion rule downwards in the list. Posunout vybrané pravidlo konverze na nižší pozici. &Add &Nové Add a number conversion rule. Vytvořit nové konverzní pravidlo. Re&move &Odstranit Remove the selected number conversion rule. Smazat vybrané konverzní pravidlo. &Edit U&pravit Edit the selected number conversion rule. Upravit vybrané konverzní pravidlo. Type a telephone number here an press the Test button to see how it is converted by the list of number conversion rules. Pro ověření funkčnosti vytvořeného konverzního pravidla sem napište zkušební telefonní číslo a stiskněte Test. &Test &Test Test how a number is converted by the number conversion rules. Otestovat, jak bude číslo převedeno konverzními pravidly. for STUN pro STUN Keep alive timer for the STUN protocol. If you have enabled STUN, then Twinkle will send keep alive packets at this interval rate to keep the address bindings in your NAT device alive. Časový signál pro STUN protokol. Pokud je STUN aktivován, bude Twinkle posílat pravidelně v zadaném časovém odstupu datové STUN-keep-alive pakety. Cílem je, aby nedošlo na routeru ke smazání příslušnosti mezi externí a interní IP adresou z adresní NAT tabulky a docházelo k pravidelnému prodlužování platnosti záznamu. Tato hodnota je proto závislá od konkrétního NATu a neměla by být příliš velká. When an incoming call is received, this timer is started. If the user answers the call, the timer is stopped. If the timer expires before the user answers the call, then Twinkle will reject the call with a "480 User Not Responding". Tento časovač je spuštěn při příchozím hovoru. Pokud nebude volání do konce vypršení časové prodlevy přijato, pak Twinkle hovor odmítne zprávou "480 User Not Responding". NAT &keep alive: &Keep alive NATu: &No answer: &Nedostupný po: Ring &back tone: Tón zv&onění u volaného: <p> Specify the file name of a .wav file that you want to be played as ring back tone for this user. </p> <p> This ring back tone overrides the ring back tone settings in the system settings. </p> <p>Zadejte zde jméno .wav souboru pro indikaci zvonění u volaného pro tohoto uživatele.</p> <p>Tento tón nahrazuje tón zvonění u volaného ze systémového nastavení.</p> <p> Specify the file name of a .wav file that you want to be played as ring tone for this user. </p> <p> This ring tone overrides the ring tone settings in the system settings. </p> <p>Zadejte zde jméno .wav souboru pro vyzváněcí tón v tomto uživatelském profilu.</p> <p>Toto nastavení nahrazuje vyzváněcí tón ze systémového nastavení.</p> &Ring tone: &Vyzváněcí tón: <p> This script is called when you release a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing SIP BYE request are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=local_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the BYE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Tento skript bude spuštěn, pokud je nějaký hovor ukončen z vaší strany. </p> <h3>Systémové proměnné</h3> <p> Obsahy všech SIP hlaviček odesílaných SIP BYE požadavků budou předány tomuto skriptu pomocí následujících systémových proměnných: </p> <p> <b>TWINKLE_TRIGGER=local_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> obsahuje request-URI metody BYE. <b>TWINKLE_USER_PROFILE</b> obsahuje jméno aktivního uživatelského profilu. <p> This script is called when an incoming call fails. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing SIP failure response are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=in_call_failed</b>. <b>SIPSTATUS_CODE</b> contains the status code of the failure response. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Tento skript bude spuštěn, pokud nebude příchozí hovor přijat. Tzn. ukončí se vyzvánění aniž by byl "zvednuto". </p> <h3>Systémové proměnné</h3> <p> Obsahy všech SIP hlaviček odesílaných SIP failure odpovědí budou předány tomuto skriptu pomocí následujících systémových proměnných: </p> <p> <b>TWINKLE_TRIGGER=in_call_failed</b>. <b>SIPSTATUS_CODE</b> obsahuje stavový kód odesílané SIP failure odpovědi. <b>SIPSTATUS_REASON</b> obsahuje "reason phrase", tedy chybovou příčinu pomocí obyčejného textu. <b>TWINKLE_USER_PROFILE</b> obsahuje jméno aktivního uživatelského profilu. <p> This script is called when the remote party releases a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming SIP BYE request are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=remote_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the BYE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Tento skript je spuštěn, pokud je hovor ukončen protistranou. </p> <h3>Systémové proměnné</h3> <p> Obsahy všech SIP hlaviček příchozích SIP BYE požadavků budou předány tomuto skriptu pomocí následujících systémových proměnných: </p> <p> <b>TWINKLE_TRIGGER=remote_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> obsahuje request-URI signálu BYE. <b>TWINKLE_USER_PROFILE</b> obsahuje jméno aktivního uživatelského profilu. <p> This script is called when the remote party answers your call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming 200 OK are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Tento skript bude spuštěn, pokud bude protistranou volání přijato. </p> <h3>Systémové proměnné</h3> <p> Obsahy všech SIP hlaviček příchozích "200 OK" hlášek budou předány tomuto skriptu pomocí následujících systémových proměnných: </p> <p> <b>TWINKLE_TRIGGER=out_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> obsahuje "reason phrase" <b>TWINKLE_USER_PROFILE</b> obsahuje jméno aktivního uživatelského profilu. <p> This script is called when you answer an incoming call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing 200 OK are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=in_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Tento skript bude spuštěn, pokud bude příchozí hovor přijmut. </p> <h3>Systémové proměnné</h3> <p> Obsahy všech SIP hlaviček odchozích "200 OK" odpovědí budou předány tomuto skriptu pomocí následujících systémových proměnných: </p> <p> <b>TWINKLE_TRIGGER=in_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> obsahuje "reason phrase" <b>TWINKLE_USER_PROFILE</b> obsahuje jméno aktivního uživatelského profilu. Call released locall&y: Lokální &ukončení hovoru: <p> This script is called when an outgoing call fails. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming SIP failure response are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call_failed</b>. <b>SIPSTATUS_CODE</b> contains the status code of the failure response. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Tento skript bude spuštěn, pokud odchozí volání nebude moci býti realizováno. Např kvůli timeout, DND atd. </p> <h3>Systémové proměnné</h3> <p> Obsah všech SIP hlaviček přijatých SIP failure odpovědí bude předán tomuto skriptu pomocí následujících systémových proměnných: </p> <p> <b>TWINKLE_TRIGGER=out_call_failed</b>. <b>SIPSTATUS_CODE</b> obsahuje stavový kód odeslané SIP failure odpovědi. <b>SIPSTATUS_REASON</b> obsahuje "reason phrase", tedy chybovou hlášku ve formě jednoduchého textu. <b>TWINKLE_USER_PROFILE</b> obsahuje jméno aktivního uživatelského profilu. <p> This script is called when you make a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing INVITE are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call</b>. <b>SIPREQUEST_METHOD=INVITE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the INVITE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Tento skript bude spuštěn, pokud bude zahájeno nějaké volání. </p> <h3>Systémové proměnné</h3> <p> Obsahy všech SIP hlaviček odeslaných SIP INVITE požadavků budou předány tomuto skriptu pomocí následujících systémových proměnných: </p> <p> <b>TWINKLE_TRIGGER=out_call</b>. <b>SIPREQUEST_METHOD=INVITE</b>. <b>SIPREQUEST_URI</b> obsahuje request-URI signálu INVITE. <b>TWINKLE_USER_PROFILE</b> obsahuje jméno aktivního uživatelského profilu. Outgoing call a&nswered: Volání &přijato protistranou: Incoming call &failed: Příchozí volání bylo &neúspěšné: &Incoming call: &Příchozí volání: Call released &remotely: Ukončení &hovoru protistranou: Incoming call &answered: Příchozí volání &přijato: O&utgoing call: Odchozí &volání: Out&going call failed: Od&chozí volání bylo neúspěšné: &Enable ZRTP/SRTP encryption Povolit &šifrování ZRTP/SRTP When ZRTP/SRTP is enabled, then Twinkle will try to encrypt the audio of each call you originate or receive. Encryption will only succeed if the remote party has ZRTP/SRTP support enabled. If the remote party does not support ZRTP/SRTP, then the audio channel will stay unecrypted. Pokud je aktivováno, pokusí se Twinkle při všech odchozích a příchozích hovorech zašifrovat zvuková data. Aby byl hovor opravdu zašifrován musí i protistrana podporovat šifrování ZRTP/SRTP. Jinak zůstane hovor nešifrovaný. ZRTP settings Nastavení ZRTP O&nly encrypt audio if remote party indicated ZRTP support in SDP &Zašifrovat, jen pokud protistrana potvrdí podporu ZRTP v SDP A SIP endpoint supporting ZRTP may indicate ZRTP support during call setup in its signalling. Enabling this option will cause Twinkle only to encrypt calls when the remote party indicates ZRTP support. Protistrana podporující ZRTP může toto oznámit již na začátku navázání rozhovoru. Pokud je aktivována tato volba, pokusí se Twinkle v takových případech použít zašifrovaný přenos hovoru. &Indicate ZRTP support in SDP ZRTP podporu ohlašovat &v SDP Twinkle will indicate ZRTP support during call setup in its signalling. Pokud je aktivováno, hlásí Twinkle protistraně při navázání hovoru pomocí SDP, že podporuje ZRTP. &Popup warning when remote party disables encryption during call &Upozornit, pokud protistrana přepne na nešifrovaný přenos hovoru A remote party of an encrypted call may send a ZRTP go-clear command to stop encryption. When Twinkle receives this command it will popup a warning if this option is enabled. Protistrana může během zašifrovaného hovoru vyslat příkaz ZRTP-go-clear a tím šifrování zrušit. Pokud je tato volba aktivována, Twinkle na tento bezpečnostní problém okamžitě upozorní. Dynamic payload type %1 is used more than once. Dynamický typ payloadu %1 je použit vícekrát. You must fill in a user name for your SIP account. Je nutné zadat uživatelské jméno pro váš SIP účet. You must fill in a domain name for your SIP account. This could be the hostname or IP address of your PC if you want direct PC to PC dialing. K vašemu SIP účtu musíte vyplnit doménové jméno) Pro přímé volání mezi IP adresami je to doménové jméno nebo veřejná IP adresa vašeho počítače. Invalid user name. Chybné uživatelské jméno. Invalid domain. Chybný název domény. Invalid value for registrar. Chybné jméno registrátora. Invalid value for outbound proxy. Chybné jméno odchozí proxy. Value for public IP address missing. Chybí veřejná IP adresa. Invalid value for STUN server. Chybný údaj STUN serveru. Ring tones Description of .wav files in file dialog Vyzváněcí tóny Choose ring tone Výběr vyzváněcího tónu Ring back tones Description of .wav files in file dialog Tón pro signalizaci vyzvánění u protistrany All files Všechny soubory Choose incoming call script Vyberte skript ke spuštění při příchozím volání Choose incoming call answered script Výběr skriptu ke spuštění po "přijetí příchozího volání" Choose incoming call failed script Vyberte skript ke spuštění po selhání příchozího volání Choose outgoing call script Vyberte skript ke spuštění při odchozím volání Choose outgoing call answered script Vyberte skript ke spuštění při přijetí volání protistranou Choose outgoing call failed script Vyberte skript ke spuštění při selhání odchozího volání Choose local release script Vyberte skript ke spuštění při vlastním ukončení hovoru Choose remote release script Vyberte skript ke spuštění při ukončení hovoru protistranou Voice mail Hlasová schránka &Follow codec preference from far end on incoming calls U příchozích hovorů dávat přednost kodekům dle pre&ferencí protistrany <p> For incoming calls, follow the preference from the far-end (SDP offer). Pick the first codec from the SDP offer that is also in the list of active codecs. <p> If you disable this option, then the first codec from the active codecs that is also in the SDP offer is picked. Pokud je aktivováno, Twinkle upřednostní při příchozím volání povolené kodeky protistrany (SDP offer). Konkrétně bude použit první kodek, který je protistranou nabízen a rovněž se nachází v seznamu lokálních kodeků. Pokud je deaktivováno, použije Twinkle první kodek ve vlastním seznamu, který je rovněž podporován protistranou. Follow codec &preference from far end on outgoing calls U odchozích hovorů dávat přednost kodekům dle pre&ferencí protistrany <p> For outgoing calls, follow the preference from the far-end (SDP answer). Pick the first codec from the SDP answer that is also in the list of active codecs. <p> If you disable this option, then the first codec from the active codecs that is also in the SDP answer is picked. Pokud je aktivováno bude Twinkle při odchozím volání řídit seznamem upřednostňovaných kodeků u protistrany (SDP answer). Konkrétně bude použit první kodek na seznamu upřednostňovaných kodeků protistrany, který je rovněž podporován v aktuálním lokálním nastavení Twinkle. Pokud je deaktivováno, použije Twinkle první kodek z vlastního seznamu, který je rovněž podporován protistranou. Tzn. je uveden v SDP-Answer seznamu. Codeword &packing order: Datové pořadí (codeword &packing order): RFC 3551 ATM AAL2 There are 2 standards to pack the G.726 codewords into an RTP packet. RFC 3551 is the default packing method. Some SIP devices use ATM AAL2 however. If you experience bad quality using G.726 with RFC 3551 packing, then try ATM AAL2 packing. Existují dvě metody balení datových paketů G.726 do RTP paketu. Standardně je to RFC 3551. Někteří SIP poskytovatelé používají ovšem ATM AAL2. Pokud je přenos zvuku při použití kodeku G.726 zarušen, je možné zde zkusit jiné nastavení. Replaces Rozšíření Replaces Indicates if the Replaces-extenstion is supported. Indikuje, zda je rozšíření Replaces podporováno. Attended refer to AoR (Address of Record) Asistovaně přepojovat na AoR (Address of Record) An attended call transfer should use the contact URI as a refer target. A contact URI may not be globally routable however. Alternatively the AoR (Address of Record) may be used. A disadvantage is that the AoR may route to multiple endpoints in case of forking whereas the contact URI routes to a single endoint. Asistované přepojení by mělo používat Contact-URI jako cílovou adresu pro sdělení nového spojení přesměrovávané protistraně. Tato adresa nemusí být ovšem globálně platná. Alternativně se může použít AoR (Address of Record). Nevýhodou je, že při více koncových zařízeních není AoR jednoznačné, zatímco URI kontaktu směřuje na jediné zařízení. Privacy Soukromí Privacy options Nastavení ochrany soukromí &Send P-Preferred-Identity header when hiding user identity &Posílat hlavičku P-Preferred-Identity při skrývání identity uživatele Include a P-Preferred-Identity header with your identity in an INVITE request for a call with identity hiding. Pokud je vybráno a je aktivována volba "skrýt odesilatele", bude spolu s údajem odesilatele odeslána při požadavku INVITE hlacička P-Preferred-Identity. <p> You can customize the way Twinkle handles incoming calls. Twinkle can call a script when a call comes in. Based on the output of the script Twinkle accepts, rejects or redirects the call. When accepting the call, the ring tone can be customized by the script as well. The script can be any executable program. </p> <p> <b>Note:</b> Twinkle pauses while your script runs. It is recommended that your script does not take more than 200 ms. When you need more time, you can send the parameters followed by <b>end</b> and keep on running. Twinkle will continue when it receives the <b>end</b> parameter. </p> <p> With your script you can customize call handling by outputting one or more of the following parameters to stdout. Each parameter should be on a separate line. </p> <p> <blockquote> <tt> action=[ continue | reject | dnd | redirect | autoanswer ]<br> reason=&lt;string&gt;<br> contact=&lt;address to redirect to&gt;<br> caller_name=&lt;name of caller to display&gt;<br> ringtone=&lt;file name of .wav file&gt;<br> display_msg=&lt;message to show on display&gt;<br> end<br> </tt> </blockquote> </p> <h2>Parameters</h2> <h3>action</h3> <p> <b>continue</b> - continue call handling as usual<br> <b>reject</b> - reject call<br> <b>dnd</b> - deny call with do not disturb indication<br> <b>redirect</b> - redirect call to address specified by <b>contact</b><br> <b>autoanswer</b> - automatically answer a call<br> </p> <p> When the script does not write an action to stdout, then the default action is continue. </p> <p> <b>reason: </b> With the reason parameter you can set the reason string for reject or dnd. This might be shown to the far-end user. </p> <p> <b>caller_name: </b> This parameter will override the display name of the caller. </p> <p> <b>ringtone: </b> The ringtone parameter specifies the .wav file that will be played as ring tone when action is continue. </p> <h2>Environment variables</h2> <p> The values of all SIP headers in the incoming INVITE message are passed in environment variables to your script. The variable names are formatted as <b>SIP_&lt;HEADER_NAME&gt;</b> E.g. SIP_FROM contains the value of the from header. </p> <p> TWINKLE_TRIGGER=in_call. SIPREQUEST_METHOD=INVITE. The request-URI of the INVITE will be passed in <b>SIPREQUEST_URI</b>. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Zde můžete upravit, jak Twinkle obslouží příchozí hovory. Twinkle může při příchozích hovorech spustit skript. V závislosti na výstupu skriptu Twinkle přijme, odmítne nebo přesměruje hovor. Skript může ovlivnit také vyzváněcí tón. Může se jednat o libovolný spustitelný program. </p> <p><b>Poznámka:</b> Twinkle přestane po dobu běhu skriptu pracovat. Doporučujeme, aby skript neběžel déle než 200 ms. Pokud potřebujete více času, můžete po odeslání parametrů poslat <b>end</b> a pokračovat v běhu. Twinkle bude sám po přijetí parametru <b>end</b> pokračovat v běhu. <h3>Vrácené hodnoty</h3> - print po STDOUT (např. `echo "action=dnd"`), jedna hodnota na řádek: <br> <tt>action=[ continue | reject | dnd | redirect | autoanswer ]<br></tt> <blockquote> <i>continue</i> - pokračovat v normálním zpracování volání (výchozí)<br> <i>reject</i> - odmítnout volání<br> <i>dnd</i> - odmítnout volání s poznámkou "nerušit"<br> <i>redirect</i> - přesměrovat volání na <tt>contact</tt><br> <i>autoanswer</i> - volání automaticky přijmout<br> </blockquote> <br> <tt>reason=&lt;string&gt; </tt>pro dnd a reject (zobrazení u protistrany)<br> <tt>contact=&lt;přesměrovací adresa&gt; </tt>pro přesměrování<br> <tt>caller_name=&lt;nové zobrazované jméno volajícího&gt; </tt>nahrazuje pro zobrazení eventuálně již existující jméno z INVITE<br> <tt>ringtone=&lt;jméno .wav souboru&gt; </tt>vyzváněcí tón speciálně pro toto volání (jen při <i>continue</i> ;-)<br> <tt>display_msg=&lt;libovolná poznámka pro podrobné zobrazení v hlavním okně&gt;</tt><br> <tt>end </tt>Twinkle vyhodnotí všechny vrácené hodnoty, uzavře STDOUT skriptu(!) a pokračuje dále<br> </tt> </p> <p> <h3>Systémové proměnné</h3> <p> Hodnoty všech SIP hlaviček příchozího INVITE budou předány tomuto skriptu. Struktura proměnných: <b>SIP_&lt;HEADER_NAME&gt;</b> - např. SIP_FROM obsahuje hodnotu "from header". </p> <p> TWINKLE_TRIGGER=in_call. <br> SIPREQUEST_METHOD=INVITE. <br> SIPREQUEST_URI obsahuje request-URI signálu INVITE.<br> TWINKLE_USER_PROFILE obsahuje jméno uživatelského profilu, pro který je příchozí volání určeno. &Voice mail address: &Adresa hlasové schránky: The SIP address or telephone number to access your voice mail. SIP adresa nebo telefonní číslo pro přístup k vaší hlasové schránce. Unsollicited Nevyžádané Sollicited Vyžádané <H2>Message waiting indication type</H2> <p> If your provider offers the message waiting indication service, then Twinkle can show you when new voice mail messages are waiting. Ask your provider which type of message waiting indication is offered. </p> <H3>Unsollicited</H3> <p> Asterisk provides unsollicited message waiting indication. </p> <H3>Sollicited</H3> <p> Sollicited message waiting indication as specified by RFC 3842. </p> <H2>Typ indikace čekajících zpráv</H2> <p> Pokud váš SIP poskytovatel nabízí upozornění na uložené zprávy v hlasové schránce, může vás Twinkle informovat o nových i již vyslechnutých zprávách ve vaší hlasové schránce. Zeptejte se vašeho poskytovatele, jaký typ indikace čekajících zpráv je používán </p> <H3>Nevyžádané</H3> <p> Asterisk podporuje nevyžádané indikování čekajících zpráv. </p> <H3>Vyžádané</H3> <p> Vyžádaná indikace čekajících zpráv dle RFC 3842. </p> &MWI type: Typ &MWI: Sollicited MWI Vyžádané MWI Subscription &duration: Doba &platnosti odběru: Mailbox &user name: Uživatelské jméno &hlasové schránky: The hostname, domain name or IP address of your voice mailbox server. Jméno hostitele, doménové jméno nebo IP adresa serveru vaší hlasové schránky. For sollicited MWI, an endpoint subscribes to the message status for a limited duration. Just before the duration expires, the endpoint should refresh the subscription. Dle specifikace MWI se koncové zařízení hlásí na serveru k příjmu zpráv na určitou dobu a před vypršením této doby by se přihlášení mělo znovu obnovit. Your user name for accessing your voice mailbox. Uživatelské jméno pro přístup k vaší hlasové schránce. Mailbox &server: &Server hlasové schránky: Via outbound &proxy Přes odchozí &proxy Check this option if Twinkle should send SIP messages to the mailbox server via the outbound proxy. Pokud je aktivováno, pak Twinkle zasílá SIP zprávy na server hlasové schránky přes odchozí proxy. You must fill in a mailbox user name. Musíte vyplnit uživatelské jméno hlasové schránky. You must fill in a mailbox server Musíte vyplnit server hlasové schránky Invalid mailbox server. Neplatný server hlasové schránky. Invalid mailbox user name. Neplatné uživatelské jméno hlasové schránky. Use domain &name to create a unique contact header value Použít doménové &jméno pro vytvoření jednoznačné hlavičky Contact Select ring back tone file. Vyberte soubor pro vyzváněcí tón u protistrany. Select ring tone file. Vyberte soubor s vyzváněcím tónem. Select script file. Vyberte soubor se skriptem. %1 converts to %2 %1 převádí na %2 Instant message Textová zpráva Presence Přítomnost &Maximum number of sessions: &Maximální počet sezení: When you have this number of instant message sessions open, new incoming message sessions will be rejected. Pokud je již otevřen tento počet sezení s textovými zprávami, nově příchozí sezení budou odmítnuta. Your presence Vaše dostupnosti &Publish availability at startup &Zveřejnit dostupnost při spuštění Publish your availability at startup. Zveřejnit vaši dostupnost při spuštění. Buddy presence Přítomnost kontaktů Publication &refresh interval (sec): Interval &odesílání stavu (s): Refresh rate of presence publications. Obnovovací frekvence odesílání vlastní dostupnosti. &Subscription refresh interval (sec): &Interval obnovení příjmu dostupnosti (s): Refresh rate of presence subscriptions. Obnovovací frekvence příjmu dostupnosti kontaktů. Transport/NAT Přenos/NAT Add q-value to registration Přidat q-hodnotu k registraci The q-value indicates the priority of your registered device. If besides Twinkle you register other SIP devices for this account, then the network may use these values to determine which device to try first when delivering a call. Hodnota 'q' určuje prioritu vašeho zaregistrovaného zařízení. Pokud je mimo Twinkle k VoIP účtu zaregistrováno jiné SIP zařízení, může síť využít těchto hodnot k určení zařízení, které bude přednostně osloveno pro obsloužení hovoru. The q-value is a value between 0.000 and 1.000. A higher value means a higher priority. Hodnota 'q' je mezi 0.000 and 1.000 Vyšší hodnota znamená vyšší prioritu. SIP transport Přenos SIP UDP UDP TCP TCP Transport mode for SIP. In auto mode, the size of a message determines which transport protocol is used. Messages larger than the UDP threshold are sent via TCP. Smaller messages are sent via UDP. Režim přenosu pro SIP. V automatickém režimu je velikost zpráv určuje, jaký transportní protokol je použit. Zprávy větší než hranice pro UDP jsou posílány přes TCP. Menší zprávy jsou posílány přes UDP. T&ransport protocol: P&řenosový protokol: UDP t&hreshold: &Hranice použití UDP: Messages larger than the threshold are sent via TCP. Smaller messages are sent via UDP. Zprávy větší než stanovená hranice jsou odeslány přes TCP. Menší zprávy přes UDP. Use &STUN (does not work for incoming TCP) Použít &STUN (nefunguje pro příchozí TCP) P&ersistent TCP connection Tr&valé TCP spojení Keep the TCP connection established during registration open such that the SIP proxy can reuse this connection to send incoming requests. Application ping packets are sent to test if the connection is still alive. Podržet otevřené TCP spojení vytvořené během registrace tak, aby SIP proxy mohla využít tohoto spojení k vysílání příchozích požadavků. Aplikace odesílá ping pakety tak, aby se zjistilo, zda-li je spojení stále aktivní. &Send composing indications when typing a message. Při psaní zprávy &odesílat o tomto posílat indikaci. Twinkle sends a composing indication when you type a message. This way the recipient can see that you are typing. Pokud píšete zprávu, pak o tomto Twinkle informuje protistranu. Takto se příjemce může dozvědět, že něco píšete. AKA AM&F: A&KA OP: Authentication management field for AKAv1-MD5 authentication. Parametry autentizačního managementu pro AKAv1-MD5 autentizaci. Operator variant key for AKAv1-MD5 authentication. Operátorová varianta klíče pro autentizaci AKAv1-MD5. Prepr&ocessing Před&zpracování Preprocessing (improves quality at remote end) Předzpracování (vylepšuje kvalitu u příjemce) &Automatic gain control &Automatické řízení hlasitosti Automatic gain control (AGC) is a feature that deals with the fact that the recording volume may vary by a large amount between different setups. The AGC provides a way to adjust a signal to a reference volume. This is useful because it removes the need for manual adjustment of the microphone gain. A secondary advantage is that by setting the microphone gain to a conservative (low) level, it is easier to avoid clipping. Z důvodu velkého rozdílu hlasitosti nahrávání v různých nastavení byla zavedena funkce automatického řízení hlasitosti (AGC - Automatic gain control). AGC umožňuje nastavit úroveň signálu na přednastavenou hodnotu. Díky tomu není nutné pokaždé manuálně nastavovat hlasitost mikrofonu. Další výhodou je, že nastavení hlasitosti mikrofonu je většinou na nižší (konzervativní) úrovni, čímž se předchází ořezávání příliš hlasitého zvuku. Automatic gain control &level: &Úroveň automatického řízení hlasitosti: Automatic gain control level represents percentual value of automatic gain setting of a microphone. Recommended value is about 25%. Úroveň automatického řízení hlasitosti představuje procentuální hodnotu maximální hlasitosti mikrofonu. Doporučená hodnota je kolem 25%. &Voice activity detection Detekce &hlasu When enabled, voice activity detection detects whether the input signal represents a speech or a silence/background noise. Pokud je aktivní, pak detekce hlasu zjišťuje, zda je na zvukovém vstupu hlas nebo ticho/šum na pozadí. &Noise reduction &Potlačení šumu The noise reduction can be used to reduce the amount of background noise present in the input signal. This provides higher quality speech. Potlačení šumu může být použito k snížení okolních rušivých zvuků ve vstupním signálu. Vede to k lepší kvalitě mluveného slova. Acoustic &Echo Cancellation Potlačení &akustické ozvěny In any VoIP communication, if a speech from the remote end is played in the local loudspeaker, then it propagates in the room and is captured by the microphone. If the audio captured from the microphone is sent directly to the remote end, then the remote user hears an echo of his voice. An acoustic echo cancellation is designed to remove the acoustic echo before it is sent to the remote end. It is important to understand that the echo canceller is meant to improve the quality on the remote end. Pokud je při VoIP komunikaci příchozí zvuk přehráván na hlasitý odposlech v reproduktorech, může se šířit místností a dostávat se zpět do mikrofonu. Pokud je tento signál poslán zpět volajícímu, stává se, že slyší ozvěnu vlastního hlasu. Funkce potlačení akustické ozvěny je navržena k potlačení těchto zvuků před tím, než jsou odeslány volajícímu. Je důležité si uvědomit, že tato funkce je určena pro zlepšení kvality přenosu hlasu na druhé straně hovoru. Variable &bit-rate Proměnný &datový tok Discontinuous &Transmission Diskontinuitní &přenos &Quality: &Kvalita: Speex is a lossy codec, which means that it achives compression at the expense of fidelity of the input speech signal. Unlike some other speech codecs, it is possible to control the tradeoff made between quality and bit-rate. The Speex encoding process is controlled most of the time by a quality parameter that ranges from 0 to 10. Speex je ztrátový kodek, což znamená, že je na úkor kvality možné docílit redukce datového toku. Na rozdíl od jiných hlasových kodeků je možné nastavit kompromis mezi kvalitou a datovým tokem. Kódovací proces u tohoto kodeku je zpravidla řízen nastavením parametru kvality v rozsahu od 0 do 10. bytes bajtů Use tel-URI for telephone &number Použít tel-URI pro &telefonní číslo Expand a dialed telephone number to a tel-URI instead of a sip-URI. Rozšířit vytáčené telefonní číslo na tel-URI namísto sip-URI. Accept call &transfer request (incoming REFER) Přijímat žádosti o &přepojení hovoru (příchozí REFER) Allow call transfer while consultation in progress Povolit přepojení hovoru v průběhu asistovaného přepojení When you perform an attended call transfer, you normally transfer the call after you established a consultation call. If you enable this option you can transfer the call while the consultation call is still in progress. This is a non-standard implementation and may not work with all SIP devices. Pokud děláte asistované přepojení, pak obvykle přepojíte hovor poté, co proběhla konzultace s protistranou. Jakmile povolíte tuto volbu, pak můžete přepojit hovor, zatímco konzultace stále probíhá. Toto je nestandardní implementace, která nemusí správně pracovat se všemi zařízeními SIP. Enable NAT &keep alive Povolit NAT &keep alive Send UDP NAT keep alive packets. Posílat UDP pakety pro udržení spojení přes NAT. If you have enabled STUN or NAT keep alive, then Twinkle will send keep alive packets at this interval rate to keep the address bindings in your NAT device alive. Pokud máte povolený STUN nebo NAT keep alive, pak bude Twinkle zasílat udržovací pakety v tomto intervalu, aby byla udržena mapování na vašem NATu. WizardForm Twinkle - Wizard Twinkle - Průvodce The hostname, domain name or IP address of the STUN server. Doménové jméno, IP adresa nebo jméno hostitele STUN serveru. S&TUN server: S&TUN server: The SIP user name given to you by your provider. It is the user part in your SIP address, <b>username</b>@domain.com This could be a telephone number. <br><br> This field is mandatory. Uživatelské jméno, které vám bylo přiděleno vaším poskytovatelem SIP. Je také první částí vaší kompletní SIP adresy <b>uzivatel</b>@domain.com Může se také jednat o telefonní číslo. <br><br> Tento údaj je povinný. &Domain*: &Doména*: Choose your SIP service provider. If your SIP service provider is not in the list, then select <b>Other</b> and fill in the settings you received from your provider.<br><br> If you select one of the predefined SIP service providers then you only have to fill in your name, user name, authentication name and password. Vyberte vašeho SIP poskytovatele a uveďte zde vaše plné jméno, vaše uživatelské SIP jméno, popřípadě přihlašovací jméno a heslo.<br> Pokud váš SIP poskytovatel není v seznamu, vyberte <b>Jiný</b> a uveďte požadované údaje. &Authentication name: &Přihlašovací jméno: &Your name: Vaše &jméno: Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Vaše přihlašovací SIP jméno. Často shodné s vaším uživatelským SIP jménem. Nicméně může být i jiné. The domain part of your SIP address, username@<b>domain.com</b>. Instead of a real domain this could also be the hostname or IP address of your <b>SIP proxy</b>. If you want direct IP phone to IP phone communications then you fill in the hostname or IP address of your computer. <br><br> This field is mandatory. Doménová část vaší úplné SIP adresy uzivatel@<b>domain.com</b>. Mimo skutečné domény se může jednat také o jméno hostitele nebo IP adresu vaší <b>SIP proxy</b>. Pro přímé volání mezi IP adresami zde můžete vyplnit jméno hostitele nebo IP adresu vašeho počítače. <br><br> Tento údaj je povinný. This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. Vaše plné jméno, např. Jan Novák. Používá se jen pro účely zobrazení. Jakmile uskutečníte hovor, tak toto jméno může být zobrazeno volanému. SIP pro&xy: SIP pro&xy: The hostname, domain name or IP address of your SIP proxy. If this is the same value as your domain, you may leave this field empty. Doménové jméno, IP adresa nebo jméno vaší proxy. Pokud se shoduje s doménou, pak je možné toto pole ponechat nevyplněné. &SIP service provider: &SIP VoIP poskytovatel: &Password: &Heslo: &User name*: &Uživatelské jméno*: Your password for authentication. Vaše přihlašovací heslo. &OK &OK Alt+O &Cancel &Zrušit Alt+C Alt+Z None (direct IP to IP calls) Žádné (přímé volání mezi IP adresami) Other Jiný User profile wizard: Průvodce uživatelským profilem: You must fill in a user name for your SIP account. Musíte zadat uživatelské jméno vašeho SIP účtu. You must fill in a domain name for your SIP account. This could be the hostname or IP address of your PC if you want direct PC to PC dialing. Je nutné zadat doménové jméno vašeho SIP účtu (část vpravo od symbolu "@"). V případě přímého volání mezi IP adresami se může jednat o jméno hostitele nebo IP adresu vašeho PC. Invalid value for SIP proxy. Neplatná hodnota pro SIP proxy. Invalid value for STUN server. Neplatná hodnota pro STUN server. YesNoDialog &Yes &Ano &No &Ne incoming_call Answer Přijmout Reject Odmítnout twinkle-1.10.1/src/gui/lang/twinkle_de.ts000066400000000000000000010445471277565361200202750ustar00rootroot00000000000000 AddressCardForm Twinkle - Address Card Twinkle - Adresseintrag &Remark: Anme&rkung: Infix name of contact. Mittlerer Name oder Titel. First name of contact. Vorname oder allg. linker Namensbestandteil. Sortierschlüssel! &First name: &Vorname: You may place any remark about the contact here. Feld für beliebige Anmerkungen. Eigene Spalte, nach der sortiert werden kann - klicken Sie hierzu einfach auf den Spaltenkopf in der Adressliste. &Phone: &Telefon: &Infix name: &Titel: Phone number or SIP address of contact. Telefonnummer oder SIP-Adresse des Kontakts. Last name of contact. Nachname oder allg. rechter Namensbestandteil. &Last name: &Nachname: &OK Alt+O Alt+O &Cancel Abbruch (Es&c) Alt+C Alt+C You must fill in a name. Sie müssen einen Namen angeben. You must fill in a phone number or SIP address. Sie müssen eine Nummer oder SIP-Adresse angeben. AddressTableModel Name Name Phone Telefon Remark Anmerkung AuthenticationForm Twinkle - Authentication Twinkle - Anmeldung user No need to translate The user for which authentication is requested. Der anzumeldende Benutzer. profile No need to translate The user profile of the user for which authentication is requested. Das anzumeldende Benutzerprofil. User profile: Benutzerprofil: User: Benutzer: &Password: &Passwort: Your password for authentication. Ihr Anmeldepasswort. Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Ihr SIP-Anmeldename. Häufig identisch mit Ihrem SIP-Nutzernamen, dann leerlassen. Falls nicht, wird Ihr Provider dies mitteilen. &User name: N&utzername : &OK &Cancel Abbruch (Es&c) Login required for realm: Login nötig für Realm: realm No need to translate The realm for which you need to authenticate. Der Realm, für den Sie sich anmelden müssen. BuddyForm Twinkle - Buddy Twinkle - Buddy Address book Adressbuch Select an address from the address book. Name und Rufnummer/SIP-Adresse aus Adressbuch kopieren. &Phone: &Telefon: Name of your buddy. Lokaler Name für Buddy-Eintrag. &Show availability Online-&Status anzeigen Alt+S Alt+S Check this option if you want to see the availability of your buddy. This will only work if your provider offers a presence agent. Wenn aktiviert, erfragt Twinkle den Online-Status (Erreichbarkeit) des Buddy. Diese Funktion muss vom Provider des Buddy und gegebenenfalls auch von Ihrem Provider durch bereitstellen eines "presence agent" im Netz unterstützt werden, um zu funktionieren. &Name: &Name: SIP address your buddy. SIP-Adresse des Buddy. &OK &OK Alt+O Alt+O &Cancel Abbruch (Es&c) Alt+C Alt+C You must fill in a name. Sie müssen einen Namen angeben. Invalid phone. Unzulässige SIP-Adresse. Failed to save buddy list: %1 Fehler beim Speichern der Buddyliste: "%1" BuddyList Availability Online-Status unknown unbekannt offline offline online online request rejected Abfrage nicht angenommen not published nicht bekanntgegeben failed to publish Bekanntgeben nicht möglich request failed Abfrage fehlgeschlagen Click right to add a buddy. Mit Rechtsklick Buddy hinzufügen. CoreAudio Failed to open sound card Fehler beim Öffnen Soundkarte Failed to create a UDP socket (RTP) on port %1 Fehler beim Erzeugen des UDP socket (RTP) für Port %1 Failed to create audio receiver thread. Fehler beim Erzeugen "audio receiver thread". Failed to create audio transmitter thread. Fehler beim Erzeugen "audio transmitter thread". CoreCallHistory local user lokal remote user Gegenstelle failure Fehler unknown unbekannt in kommend out gehend DeregisterForm Twinkle - Deregister Twinkle - Abmelden deregister all devices alle Endgeräte abmelden &OK &Cancel Abbruch (Es&c) DiamondcardProfileForm &Cancel Abbruch (Es&c) Fill in your account ID. Fill in your PIN code. A user profile with name %1 already exists. DtmfForm Twinkle - DTMF Keypad Tastatur 2 3 Over decadic A. Normally not needed. Funktionstaste A. Selten benötigt. 4 5 6 Over decadic B. Normally not needed. Funktionstaste B. Selten benötigt. 7 8 9 Over decadic C. Normally not needed. Funktionstaste C. Selten benötigt. Star (*) Stern (*) 0 Pound (#) Raute (#) Over decadic D. Normally not needed. Funktionstaste D. Selten benötigt. 1 &Close S&chliessen Alt+C Alt+C FreeDeskSysTray Show/Hide Wiederherstellen/Minimieren Quit Beenden GUI Failed to create a UDP socket (SIP) on port %1 Fehler beim Erzeugen des UDP socket (SIP) für Port %1 The following profiles are both for user %1 Die folgenden Benutzerprofile verwenden den gleichen SIP-Benutzernamen "%1" You can only run multiple profiles for different users. Gleichzeitig aktive Benutzerprofile müssen eindeutige SIP-Benutzernamen haben. Cannot find a network interface. Twinkle will use 127.0.0.1 as the local IP address. When you connect to the network you have to restart Twinkle to use the correct IP address. Twinkle kann kein aktives Netzwerk-Interface finden, und nutzt nun 127.0.0.1 als lokale IP-Adresse. Wenn Sie später eine Netzwerkverbindung herstellen, müssen Sie Twinkle neu starten, damit es die korrekte Netzadresse finden und funktionieren kann. Line %1: incoming call for %2 Leitung %1: eingehender Ruf für %2 Call transferred by %1 Ruf weitervermittelt durch %1 Line %1: far end cancelled call. Leitung %1: Gegenstelle hat Ruf abgebrochen. Line %1: far end released call. Leitung %1: beendet durch Gegenstelle. Line %1: SDP answer from far end not supported. Leitung %1: SDP Antwort der GgSt. nicht unterstützt. Line %1: SDP answer from far end missing. Leitung %1: keine SDP Antwort der Gegenstelle. Line %1: Unsupported content type in answer from far end. Leitung %1: Inhaltstyp in Antwort der Ggst nicht unterstützt. Line %1: no ACK received, call will be terminated. Leitung %1: kein ACK von Ggst, Ruf wird beendet. Line %1: no PRACK received, call will be terminated. Leitung %1: kein PRACK von Ggst, Ruf wird beendet. Line %1: PRACK failed. Leitung %1: PRACK Fehler. Line %1: failed to cancel call. Leitung %1: Fehler bei Ruf beenden. Line %1: far end answered call. Leitung %1: Ggst hat angenommen. Line %1: call failed. Leitung %1: Ruf erfolglos. The call can be redirected to: Der Ruf kann umgeleitet werden nach: Line %1: call released. Leitung %1: Anruf beendet. Line %1: call established. Leitung %1: Verbindung hergestellt. Response on terminal capability request: %1 %2 Antwort GgSt auf Abfrage der Eigenschaften: %1 %2 Terminal capabilities of %1 Fähigkeiten Gegenstelle %1 Accepted body types: Erlaubte "body types": unknown unbekannt Accepted encodings: Erlaubte "encodings": Accepted languages: Erlaubte Sprachen: Allowed requests: Erlaubte "requests": Supported extensions: Unterstützte "extensions": none keine End point type: Endgeräte-Typ: Line %1: call retrieve failed. Leitung %1: Fehler bei "Gespräch fortsetzen". %1, registration failed: %2 %3 %1, Anmeldung erfolglos: %2 %3 %1, registration succeeded (expires = %2 seconds) %1, Anmeldung erfolgreich (gültig %2 Sek.) %1, registration failed: STUN failure %1, Anmeldung erfolglos: STUN Fehler %1, de-registration succeeded: %2 %3 %1, Abmeldung erfolgreich: %2 %3 %1, fetching registrations failed: %2 %3 %1, Fehler bei Abfrage Anmeldungen: %2 %3 : you are not registered : Sie sind nicht angemeldet : you have the following registrations : folgende Anmeldungen aktiv : fetching registrations... : Abfrage Anmeldungen läuft... Line %1: redirecting request to Leitung %1: leite Anfrage um nach Redirecting request to: %1 Leite Anfrage um nach: %1 Line %1: DTMF detected: Leitung %1: DTMF empfangen: invalid DTMF telephone event (%1) Ungültiges DTMF-Ereignis (%1) Line %1: send DTMF %2 Leitung %1: sende DTMF %2 Line %1: far end does not support DTMF telephone events. Leitung %1: GgSt unterstützt keine DTMF-Ereignisse. Line %1: received notification. Leitung %1: Mitteilung empfangen. Event: %1 Ereignis: %1 State: %1 Status: %1 Reason: %1 Ursache: %1 Progress: %1 %2 Fortschritt: %1 %2 Line %1: call transfer failed. Leitung %1: Rufweitervermittlung erfolglos. Line %1: call successfully transferred. Leitung %1: Ruf wurde weitervermittelt. Line %1: call transfer still in progress. Leitung %1: Rufweitervermittlung läuft... No further notifications will be received. Gegenstelle stoppt Mitteilungsversand. Line %1: transferring call to %2 Leitung %1: Rufweitervermittlung an %2 Transfer requested by %1 Rufweitervermittlung angefordert von %1 Line %1: Call transfer failed. Retrieving original call. Leitung %1: Rufweitervermittlung erfolglos. Ursprüngliches Gespräch wird fortgesetzt. Redirecting call Ruf wird umgeleitet User profile: Benutzerprofil: User: Benutzer: Do you allow the call to be redirected to the following destination? Möchten Sie Rufumleitung zu folgendem Ziel gestatten? If you don't want to be asked this anymore, then you must change the settings in the SIP protocol section of the user profile. In den Benutzerprofil-Einstellungen unter "SIP-Protokoll" können Sie festlegen, ob diese Frage angezeigt wird oder nicht. Redirecting request Leite Anfrage um Do you allow the %1 request to be redirected to the following destination? Möchten Sie die Umleitung der %1-Anforderung zu folgendem Ziel gestatten? Transferring call Ruf wird weitervermittelt Request to transfer call received from: Weitervermittlung angefordert durch: Do you allow the call to be transferred to the following destination? Möchten Sie Rufweitervermittlung zu folgendem Ziel gestatten? Info: Info: Warning: Warnung: Critical: Kritisch: Firewall / NAT discovery... Firewall / NAT Analyse... Abort Abbrechen Line %1 Leitung %1 Click the padlock to confirm a correct SAS. Klicken Sie auf das Vorhängeschloss, um korrektes SAS-Geheimwort zu bestätigen. The remote user on line %1 disabled the encryption. Die Gegenstelle an Leitung %1 hat Verschlüsselung abgeschaltet. Line %1: SAS confirmed. Leitung %1: SAS bestätigt. Line %1: SAS confirmation reset. Leitung %1: SAS Bestätigung gelöscht. Line %1: call rejected. Leitung %1: Ruf abgelehnt. Line %1: call redirected. Leitung %1: Ruf umgeleitet. Failed to start conference. Konferenz konnte nicht geschaltet werden. Override lock file and start anyway? Sperrdatei ignorieren und trotzdem starten? %1, STUN request failed: %2 %3 %1, STUN Anfrage fehlgeschlagen: %2 %3 %1, STUN request failed. %1, STUN Anfrage fehlgeschlagen. %1, voice mail status failure. %1, Fehler Voice-Mail Status. %1, voice mail status rejected. %1, Voice-Mail Status abgelehnt. %1, voice mailbox does not exist. %1, Voice-Mailbox existiert nicht. %1, voice mail status terminated. %1, keine weitere Voice-Mail Statusübermittlung. %1, de-registration failed: %2 %3 %1, Abmeldung erfolglos: %2 %3 Request to transfer call received. GgSt fordert Vermittlung an. If these are users for different domains, then enable the following option in your user profile (SIP protocol) Wenn diese Profile unterschiedliche Domains verwenden, bitte in einem unter SIP-Protololl folgende Option aktivieren Use domain name to create a unique contact header Domain-Name benutzen für eindeutigen Contact-Header Failed to create a %1 socket (SIP) on port %2 Fehler beim Anlegen: %1 socket (SIP) auf port %2 Accepted by network Akzeptiert durch Netzwerk Failed to save message attachment: %1 Fehler beim Speichern des Nachrichtenanhangs: "%1" Transferred by: %1 Cannot open web browser: %1 Configure your web browser in the system settings. GetAddressForm Twinkle - Select address Twinkle - Auswahl Adresse Name Name Type Typ Phone Telefon &Show only SIP addresses Nur &SIP-Adressen zeigen Alt+S Alt+S Check this option when you only want to see contacts with SIP addresses, i.e. starting with "<b>sip:</b>". Wenn aktiviert, werden nur Kontakte angezeigt, die eine gültige SIP-Adresse enthalten, also beginnend mit "<b>sip:</b>". &Reload Aktualisie&ren Alt+R Alt+R Reload the list of addresses from KAddressbook. Adressliste aus KAddressbook erneut einlesen.<br> Ein Schliessen und erneutes Öffnen des Fensters führt <i>nicht</i> zum Neueinlesen.<br> Änderungen im Adressbestand werden erst durch "Aktualisieren" in Twinkle sichtbar. &OK Alt+O Alt+O &Cancel Abbruch (Es&c) Alt+C Alt+C &KAddressBook This list of addresses is taken from <b>KAddressBook</b>. Contacts for which you did not provide a phone number are not shown here. To add, delete or modify address information you have to use KAddressBook. Diese Adressliste stammt aus <b>KAddressbook</b> (bzw Kontact). Adressen/Kontakte, die keine Telefonnr oder SIP-Adresse enthalten, sind nicht aufgeführt. Nutzen Sie zum Anlegen und Bearbeiten Ihrer systemweiten Adressinformationen das Programm KAddressbook bzw Kontact. &Local address book &Lokales Adressbuch Remark Anmerkung Contacts in the local address book of Twinkle. Kontakte des lokalen Twinkle-Adressbuchs. &Add &Neu Alt+A Alt+N Add a new contact to the local address book. Neuen Kontakt im lokalen Adressbuch anlegen. &Delete &Löschen Alt+D Alt+L Delete a contact from the local address book. Ausgewählten Kontakt aus dem lokalen Adressbuch löschen. &Edit B&earbeiten Alt+E Alt+E Edit a contact from the local address book. Ausgewählten Kontakt im lokalen Adressbuch bearbeiten. <p>You seem not to have any contacts with a phone number in <b>KAddressBook</b>, KDE's address book application. Twinkle retrieves all contacts with a phone number from KAddressBook. To manage your contacts you have to use KAddressBook.<p>As an alternative you may use Twinkle's local address book.</p> <p><b>KAddressbook</b> bzw <b>Kontact</b> scheint keine Einträge mit Telefonnr zu enthalten, die Twinkle dort auslesen könnte. Bitte nutzen Sie eines dieser Programme, um Ihre Adressdaten zu bearbeiten.</p> <p>Weiterhin steht Ihnen Twinkles lokales Adressbuch unabhängig von o.g. Programmen zur Verfügung.</p> GetProfileNameForm Twinkle - Profile name Twinkle - Name Benutzerprofil &OK &Cancel Abbruch (Es&c) Enter a name for your profile: Name für das neue Profil: <b>The name of your profile</b> <br><br> A profile contains your user settings, e.g. your user name and password. You have to give each profile a name. <br><br> If you have multiple SIP accounts, you can create multiple profiles. When you startup Twinkle it will show you the list of profile names from which you can select the profile you want to run. <br><br> To remember your profiles easily you could use your SIP user name as a profile name, e.g. <b>example@example.com</b> Der <b>Name, unter dem das neue Profil angelegt</b> wird, in dem dann alle zusammengehörenden Daten wie Provider, SIP-Nutzername, Passwort usw gespeichert werden. (entsprechend z.B. einer "Identität" unter KMail)<br><br> Da Sie bei Twinkle mehrere Benutzerprofile anlegen können, z.B. um mehrere SIP-Provider zu nutzen, muss jedes Profil einen Namen erhalten. Unter diesem Namen finden Sie es später in Auswahllisten, Meldungen usw.<br> Es bietet sich an, hier Ihre SIP-Adresse als Name zu verwenden, also <b>meinname@meinprovider.de</b>, aber Sie können letztendlich beliebige Namen wählen.<br> <p> <b>Bevor Sie hier Ihr erstes SIP-Profil anlegen, sollten Sie sich bei einem SIP-Provider (vertraglich) angemeldet haben</b> und sich notieren, welche <b>SIP-Zugangsdaten</b> dieser für Sie zur Verfügung gestellt hat. </p> Cannot find .twinkle directory in your home directory. Kann den Ordner ".twinkle" in Ihrem home-Ordner ("/home/ihrname/") nicht finden. Profile already exists. Profil mit diesem Namen existiert bereits. Rename profile '%1' to: Profil "%1" umbenennen in: HistoryForm Twinkle - Call History Twinkle - Liste Anrufe Time Uhreit In/Out Ank/Abg From/To Gegenstelle Subject Betreff Status Status Call details Details Details of the selected call record. Details zum ausgewählten Anruf. View Anzeigen &Incoming calls E&ingehende Anrufe Alt+I Alt+I Check this option to show incoming calls. Wenn aktiviert, werden Anrufe angezeigt bei denen Sie von jemand angerufen wurden. &Outgoing calls Ab&gehende Anrufe Alt+O Alt+G Check this option to show outgoing calls. Wenn aktiviert, werden Anrufe angezeigt die Sie selbst getätigt haben. &Answered calls Be&antwortete Anrufe Alt+A Alt+A Check this option to show answered calls. Wenn aktiviert, werden Anrufe angezeigt die zustande kamen. &Missed calls A&nrufversuche Alt+M Alt+N Check this option to show missed calls. Wenn aktiviert, werden Anrufe angezeigt die nicht zustande kamen. Current &user profiles only N&ur aktive Benutzerprofile Alt+U Alt+U Check this option to show only calls associated with this user profile. Wenn aktiviert, nur Anrufe zeigen, die mit/zu einem der aktuell aktivierten Benutzerprofile getätigt wurden. C&lear &Liste löschen Alt+L Alt+L <p>Clear the complete call history.</p> <p><b>Note:</b> this will clear <b>all</b> records, also records not shown depending on the checked view options.</p> Löscht das gesamte Anrufe-Protokoll,<br> <b>inklusive</b> aller evtl gerade über "Anzeigen" <b>ausgeblendeten Einträge.</b> Alt+C Alt+E Close this window. Fenster schliessen. Call start: Anruf Start: Call answer: Angenommen: Call end: Anruf Ende: Call duration: Anrufdauer: Direction: Richtung: From: Von: To: An: Reply to: Antwort auf: Referred by: Über: Subject: Betreff: Released by: Beendet von: Status: Status: Far end device: Typ Gegenstelle: User profile: Benutzerprofil: conversation Gespräch Call... Anrufen... (Doppelklick) Delete Eintrag löschen Re: Aw: Call selected address. Markierte Adresse/Nummer anrufen. Clo&se &Schliessen (Esc) Alt+S Alt+S &Call Anrufen (&Enter) Number of calls: ### Total call duration: IncomingCallPopup %1 calling InviteForm Twinkle - Call Twinkle - Anrufen &To: An (&Telnr): Optionally you can provide a subject here. This might be shown to the callee. Sie können hier einen Betreff angeben, der ebenso wie Ihr Displayname von der Gegenstelle angezeigt werden kann. Address book Adressbuch Select an address from the address book. Adresse/Nr aus dem KDE-Adressbuch auswählen. The address that you want to call. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Der Anschluss, den Sie anrufen möchten. Dies kann eine vollständige SIP-Adresse sein, wie <b>sip:example@example.com</b>, oder auch nur eine Telephonnummer bzw. der Benutzername einer SIP-Adresse, dann ergänzt Twinkle sie mit der im Benutzerprofil eingetragenen Domain zur vollständigen SIP-Adresse. The user that will make the call. Das Benutzerprofil -und damit der Provider- mit dem der Ruf gestartet wird. &Subject: &Betreff: &From: &Von: &OK &Cancel Abbruch (Es&c) &Hide identity Absenderangaben &unterdrücken Alt+H Alt+U <p> With this option you request your SIP provider to hide your identity from the called party. This will only hide your identity, e.g. your SIP address, telephone number. It does <b>not</b> hide your IP address. </p> <p> <b>Warning:</b> not all providers support identity hiding. </p> <p>Mit dieser Option weisen Sie Ihren SIP-Provider an, Ihre Absenderangaben (z.B. Telefonnr, SIP-Adresse) nicht an die Gegenstelle weiterzuleiten. Prinzipbedingt wird Ihre IP-Adresse <b>immer</b> der Gegenstelle mitgeteilt.</p> <p><b>Achtung: </b>Nicht alle Provider unterstützen diese Funktion!</p> Not all SIP providers support identity hiding. Make sure your SIP provider supports it if you really need it. Nicht alle SIP-Provider unterstützen die Funktion "Absenderangaben unterdrücken". Bitte vergewissern Sie sich, bevor Sie sich auf diese Funktion verlassen. F10 LogViewForm Twinkle - Log Contents of the current log file (~/.twinkle/twinkle.log) Inhalt der aktuellen Logdatei (~/.twinkle/twinkle.log) &Close S&chliessen Alt+C Alt+C C&lear &Löschen Alt+L Alt+L Clear the log window. This does <b>not</b> clear the log file itself. Die Anzeige des Fensters löschen. Die Logdatei selbst wird <b>nicht</b> gelöscht oder geleert. MessageForm Twinkle - Instant message Twinkle - Instant Message &To: &An (Adr): The user that will send the message. Als Absender verwendetes Benutzerprofil. The address of the user that you want to send a message. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Die Adresse/Nummer der Gegenstelle, an die Sie eine Instant Message senden möchten. Wie immer bei Twinkle kann dies eine vollständige Adresse oder ein Username sein. Wenn Sie nur den Usernamen angeben, ergänzt Twinkle die Domain aus dem verwendeten Absender-Benutzerprofil. Address book Adressbuch Select an address from the address book. Adresse/Nr aus dem KDE-Adressbuch auswählen. &User profile: Ben&utzerprofil: Conversation Dialog The exchanged messages. Die gesendeten und empfangenen Nachrichten. Gesendete schwarz, empfangene blau. Type your message here and then press "send" to send it. Schreiben Sie hier Ihre Nachricht und klicken Sie "senden" oder drücken Sie "Enter" zum abschicken. &Send &Senden Alt+S Alt+S Send the message. Nachricht senden. Delivery failure Übertragungsfehler Delivery notification Übertragungsbestätigung Instant message toolbar Instant Message Werkzeugleiste Send file... Sende Datei... Send file Sende Datei image size is scaled down in preview Bild in Vorschau verkleinert Open with %1... Öffnen mit %1... Open with... Öffnen mit... Save attachment as... Anhang speichern unter... File already exists. Do you want to overwrite this file? Datei dieses Namens existiert bereits! Löschen und durch neue Datei ersetzen? Failed to save attachment. Fehler beim Speichern des Anhangs. %1 is typing a message. %1 schreibt gerade eine Nachricht. F10 Size MessageFormView sending message Nachricht wird gesendet MphoneForm Twinkle &Call: Label in front of combobox to enter address &Nummer: The address that you want to call. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Der Anschluss, den Sie anrufen möchten. Dies kann eine vollständige SIP-Adresse sein, wie <b>sip:example@example.com</b>, oder auch nur eine Telephonnummer bzw. der Benutzername einer SIP-Adresse, dann ergänzt Twinkle sie mit der im Benutzerprofil eingetragenen Domain zur vollständigen SIP-Adresse. The user that will make the call. Das Benutzerprofil -und damit der Provider- mit dem der Ruf gestartet wird. &User: &Profil: Dial Wählen Dial the address. Startet den Anruf . Address book Adressbuch Select an address from the address book. Rufnummer/SIP-Adresse aus Adressbuch wählen. Auto answer indication. Anzeige und Einstellen des Dienstes "Automatisch annehmen". Message waiting indication. Anzeige der Nachrichten auf Voice-Mailboxen; Anklicken zum Abhören der Nachrichten. Call redirect indication. Anzeige und Einstellen des Dienstes "Rufumleitung". Do not disturb indication. Anzeige und Einstellen des Dienstes "Bitte nicht stören". Missed call indication. Anzeige "Anrufe in Abwesenheit" und Öffnen der Anruferliste. Registration status. Anzeige und Abruf der Anmeldezustände. Die Ergebnisse des Abrufs werden in der Detailanzeige dargestellt. Display Detailanzeige Line status Leitungsstatus Line &1: Leitung &1: Alt+1 Alt+1 Click to switch to line 1. Anklicken (oder Alt+1), um auf Leitung 1 zu schalten. From: Von: To: An: Subject: Betreff: Visual indication of line state. Optische Anzeige des Leitungsstatus. idle No need to translate Call is on hold Anruf gehalten Voice is muted stummgeschaltet Conference call Konferenz Transferring call Ruf wird weitervermittelt <p> The padlock indicates that your voice is encrypted during transport over the network. </p> <h3>SAS - Short Authentication String</h3> <p> Both ends of an encrypted voice channel receive the same SAS on the first call. If the SAS is different at each end, your voice channel may be compromised by a man-in-the-middle attack (MitM). </p> <p> If the SAS is equal at both ends, then you should confirm it by clicking this padlock for stronger security of future calls to the same destination. For subsequent calls to the same destination, you don't have to confirm the SAS again. The padlock will show a check symbol when the SAS has been confirmed. </p> <p> Das Vorhängeschloss erscheint, wenn eine abhörsicher verschüsselte Verbindung zur Übertragung der Sprachdaten aufgebaut werden konnte. </p> <h3>SAS - Short Authentication String</h3> <p> Beide Teilnehmer eines verschlüsselten Gesprächs bekommen bei der ersten Kontaktaufnahme den SAS angezeigt, einen nicht fälschbaren eindeutigen "Fingerabdruck" der ausgehandelten Verschlüsselung. Durch Vergleich dieses SAS können Sie und Ihr Gesprächspartner sicherstellen, dass Sie tatsächlich <i>direkt</i> miteinander verbunden sind. Stichwort "man-in-the-middle attack" (MitM). </p> <p> Da ein Angreifer schlecht mitten im Gespräch die Stimme Ihres Gesprächspartners imitieren kann, reicht es völlig, beim ersten Telefonat den SAS vorzulesen. Bei Übereinstimmung klicken Sie auf das Vorhängeschloss, und Twinkle merkt sich die (den "Ausweis" der) Gegenstelle als "persönlich bekannt" und lässt sich bei zukünftigen Anrufen von/zu dieser GgSt nicht täuschen ("Ausweiskontrolle"). Das Schloss wird mit einem Häkchen dargestellt, und signalisiert so, dass die GgSt auf ihre Identität überprüft und eindeutig erkannt wurde, und also eine direkte Verbindung besteht. </p> <p>Klick auf ein Schloss <i>mit</i> Häkchen löscht die Vertrauensbeziehung und Sie können/müssen den SAS neu vergleichen</p> sas No need to translate Short authentication string SAS - Geheimwort (Short authentication string) g711a/g711a No need to translate Audio codec 0:00:00 Call duration Anrufdauer sip:from No need to translate sip:to No need to translate subject No need to translate photo No need to translate Line &2: Leitung &2: Alt+2 Alt+2 Click to switch to line 2. Anklicken (oder Alt+2), um auf Leitung 2 zu schalten. &File &Datei &Edit B&earbeiten C&all &Anruf Activate line Leitung auswählen &Registration A&nmeldung &Services Dien&ste &View Ans&icht &Help &Hilfe Call Toolbar Anruf Werkzeugleiste Quit Abmelden und Twinkle beenden &Quit B&eenden Ctrl+Q About Twinkle Über Twinkle &About Twinkle Ü&ber Twinkle Call someone Anrufen - erweiterte Nummerneingabe, Betreff... F5 Answer incoming call Anruf entgegennehmen - landläufig "Abheben" F6 Release call Anruf beenden Reject incoming call Eingehenden Ruf ablehnen F8 Put a call on hold, or retrieve a held call Ein Gespräch halten, oder ein gehaltenes fortsetzen Redirect incoming call without answering Eingehenden Ruf umleiten ohne Gesprächsannahme Open keypad to enter digits for voice menu's Öffnet eine Wähltastatur zur Eingabe von Tastenbefehlen - für Steuerung von zB. Anrufbeantwortern Register Anmelden beim SIP-Sever &Register An&melden Deregister Abmelden &Deregister Abmel&den Deregister this device Dieses Telefon abmelden Show registrations Anmeldungen bei den Servern abfragen &Show registrations Anmeldungen &zeigen Terminal capabilities Fähigkeiten Gegenstelle Request terminal capabilities from someone Abfrage der "terminal capabilities", der Eigenschaften einer Gegenstelle Do not disturb Bitte nicht stören &Do not disturb &Bitte nicht stören Call redirection Rufumleitung Call &redirection... &Rufumleitung... Repeat last call Wahlwiederholung, wählt letzten Ruf erneut F12 About Qt Über Qt About &Qt Über &Qt User profile Benutzerprofil bearbeiten &User profile... Ben&utzerprofil... Join two calls in a 3-way conference Leitung1, 2 und lokal zu einer 3er Konferenz zusammenschalten Mute a call Das Mikrofon für diese Leitung ab- oder wieder anschalten Transfer call Gespräch weitervermitteln System settings Systemeinstellungen bearbeiten &System settings... &Systemeinstellungen... Deregister all Abmelden alle Endg Deregister &all &Abmelden alle Endger. Deregister all your registered devices Abmelden aller Geräte unter dieser Benutzerkennung Auto answer Autom. Annehmen &Auto answer &Autom. Annehmen Log SystemLog anzeigen &Log... Call history Liste der letzen Anrufe anzeigen Call &history... Liste aller Anru&fe... F9 Change user ... Benutzerprofile ... &Change user ... &Benutzerprofile ... Activate or de-activate users Benutzerprofile de/aktivieren, bearbeiten usw. What's This? "Was ist das?"-Kontexthilfe What's &This? Was ist &das? Shift+F1 Shift+F1 Line 1 Leitung 1 Line 2 Leitung 2 idle frei dialing wählt attempting call, please wait versuche Rufaufbau, bitte warten incoming call ankommender Ruf establishing call, please wait Verbindungsaufbau, bitte warten established Verbindung hergestellt established (waiting for media) Verbindung hergestellt (warte auf Daten) releasing call, please wait trenne Verbindung, bitte warten unknown state unbekannter Status Voice is encrypted Sprachübertragung verschlüsselt Click to confirm SAS. SAS bestätigen. Click to clear SAS verification. SAS Bestätigung löschen. User: Benutzer: Call: Ruf: Registration status: Anmeldungsstatus: Registered angemeldet Failed fehlgeschlagen Not registered nicht angemeldet No users are registered. Kein Benutzer angemeldet. Do not disturb active for: "Bitte nicht stören" aktiviert für: Redirection active for: Rufumleitung aktiviert für: Auto answer active for: "Automatisch annehmen" aktiviert für: Do not disturb is not active. "Bitte nicht stören" nicht aktiviert. Redirection is not active. Rufumleitung nicht aktiviert. Auto answer is not active. "Automatisch annehmen" nicht aktiviert. You have no missed calls. Keine Anrufe in Abwesenheit. You missed 1 call. 1 Anruf in Abwesenheit. You missed %1 calls. %1 Anrufe in Abwesenheit. Click to see call history for details. Anklicken öffnet Anrufliste mit Details . Starting user profiles... Starte Benutzerprofile... The following profiles are both for user %1 Die folgenden Benutzerprofile verwenden die gleiche SIP-Adresse %1 You can only run multiple profiles for different users. Sie können nicht für einen SIP-Account gleichzeitig mehrere Profile aktivieren. You have changed the SIP UDP port. This setting will only become active when you restart Twinkle. Der SIP UDP port wurde geändert, dies wird erst beim nächsten Start von Twinkle wirksam. Esc Esc Transfer consultation Rückfrage Hide identity Absenderangaben unterdrücken Click to show registrations. Anklicken: Anmeldungen abfragen. %1 new, 1 old message %1 neue, 1 alte Mitteilung %1 new, %2 old messages %1 neue, %2 alte Mitteilungen 1 new message 1 neue Mitteilung %1 new messages %1 neue Mitteilungen 1 old message 1 alte Mitteilung %1 old messages %1 alte Mitteilungen Messages waiting Mitteilungen da No messages Keine Mitteilungen <b>Voice mail status:</b> <b>Voice-Mail Status:</b> Failure Fehler Unknown Unbekannt Click to access voice mail. Anklicken: Voice-Mail abrufen. Click to activate/deactivate Anklicken: (de/)aktivieren Click to activate Anklicken: aktivieren not provisioned nicht eingetragen You must provision your voice mail address in your user profile, before you can access it. Sie müssen die Adresse/Nr Ihres Anrufbeantworters im Profil eintragen, damit dies geht. The line is busy. Cannot access voice mail. Kann Voice-Mail nicht abrufen - Leitung belegt. The voice mail address %1 is an invalid address. Please provision a valid address in your user profile. Die Voice-Mail-Adresse "%1" is ungültig. Bitte korregieren Sie die Einstellungen im Benutzerprofil. Call toolbar text Anruf+ &Call... call menu text Anruf+... (&Call) Answer toolbar text Ja? &Answer menu text &Annehmen Bye toolbar text Ende &Bye menu text Auflegen (&Bye) Reject toolbar text Nein! &Reject menu text Ab&weisen Hold toolbar text Halten &Hold menu text &Halten Redirect toolbar text Umleit R&edirect... menu text Uml&eiten... Dtmf toolbar text DTMF &Dtmf... menu text &DTMF... &Terminal capabilities... menu text &Fähigkeiten Gegenstelle... Redial toolbar text -> -> &Redial menu text Wahlwiederholun&g Conf toolbar text 3er-K. &Conference menu text Konferen&z Mute toolbar text Stumm &Mute menu text Stu&mm Xfer toolbar text Vmtlg Trans&fer... menu text Vermitte&ln... Voice mail Anrufbeantworter &Voice mail A&nrufbeantworter Access voice mail Voice-Mailbox abfragen F11 Buddy list Buddyliste &Message &Mitteilung Msg Msg Instant &message... Instant &Message... Instant message Instant Message senden &Call... Anrufen (&Call)... &Edit... B&earbeiten... &Delete &Löschen O&ffline O&ffline &Online &Online &Change availability Online-&Status ändern &Add buddy... Neuen Buddy &anlegen... Failed to save buddy list: %1 Fehler beim Speichern der Buddyliste: "%1" You can create a separate buddy list for each user profile. You can only see availability of your buddies and publish your own availability if your provider offers a presence server. Die Liste Ihrer <b>Benutzerprofile (fett)</b> und Buddies <i>(deutsch: "Kumpels". Ihre wichtigen Kontakte)</i> für die einzelnen Profile.<br> Über das Kontextmenü des einzelnen Benutzerprofils erzeugen Sie neue Buddy-Einträge und stellen Ihren eigenen Online-Status ein.<br> Der Online-Status der Buddies wird durch gelbe (=online) und graue (=offline) Icons dargestellt. Details erscheinen, wenn Sie den Cursor über das Icon stellen.<br> <br> Um Ihren eigenen Online-Status zu veröffentlichen, brauchen Sie die Unterstützung durch einen öffentlichen "presence server" <i>Ihres</i> Providers.<br> Um den Online-Status eines Buddies abzufragen, muss <i>dessen</i> Provider einen "presence server" im Netz ereichbar halten, und dieser muss Ihre Abfrage gestatten. Es ist daher sinnvoll, Buddies mit einem bestimmten Provider (thomas@<b>DerProvider.de</b>) unter einem gültigen eigenen Benutzerprofil mit dem selben Provider (ich.selber@<b>DerProvider.de</b>) anzulegen, da viele "presence server" nur dann die Abfrage gestatten. &Buddy list &Buddyliste &Display &Detailanzeige F10 Diamondcard Manual &Manual Sign up &Sign up... Recharge... Balance history... Call history... Admin center... Recharge Balance history Admin center Call Anruf+ &Answer &Annehmen Answer &Bye Auflegen (&Bye) Bye Ende &Reject Ab&weisen Reject &Hold &Halten Hold Halten R&edirect... Uml&eiten... Redirect Umleit &Dtmf... &DTMF... Dtmf DTMF &Terminal capabilities... &Fähigkeiten Gegenstelle... &Redial Wahlwiederholun&g Redial -> -> &Conference Konferen&z Conf 3er-K. &Mute Stu&mm Mute Stumm Trans&fer... Vermitte&ln... Xfer Vmtlg NumberConversionForm Twinkle - Number conversion Twinkle - Umwandlung Rufnummer &Match expression: &Suchausdruck: &Replace: &Ersetzung: Perl style format string for the replacement number. Formatstring wie in Perl für die Ersetzung der Nummer. Perl style regular expression matching the number format you want to modify. Regulärer Ausdruck (Perl regex), der die zu ändernde Rufnummer beschreibt. &OK Alt+O Alt+O &Cancel Abbruch (Es&c) Alt+C Alt+C Match expression may not be empty. Leerer Suchausdruck ist ungültig. Replace value may not be empty. Leerer Ersetzungsausdruck ist ungültig. Invalid regular expression. Ungültige regular expression. RedirectForm Twinkle - Redirect Twinkle - Rufumleitung Redirect incoming call to Ankommenden Ruf umleiten nach You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. Es können bis zu 3 Ziele für die Rufumleitung angegeben werden. Wird der Ruf vom ersten Ziel nicht angenommen, werden das zweite und dann das dritte verwendet. &3rd choice destination: &3. Ziel: &2nd choice destination: &2. Ziel: &1st choice destination: &1. Ziel: Address book Adressbuch Select an address from the address book. Rufnummer/SIP-Adresse aus Adressbuch wählen. &OK &Cancel Abbruch (Es&c) F10 F12 F11 SelectNicForm Twinkle - Select NIC Twinkle - Netzwerkanschluss wählen Select the network interface/IP address that you want to use: Bitte wählen Sie den zu verwendenden Anschluss / IP-Adr.: You have multiple IP addresses. Here you must select which IP address should be used. This IP address will be used inside the SIP messages. Auf Ihrem Rechner sind mehrere IP-Adressen verfügbar. Bitte wählen Sie diejenige, unter der Ihr Rechner aus dem Internet bzw -wenn Sie einen Router verwenden- in ihrem lokalen Netz erreichbar ist. Diese IP-Adresse verwendet Twinkle dann in den SIP-Datenpaketen als Absenderangabe. Set as default &IP &IP-Adr. als Standard Alt+I Alt+I Make the selected IP address the default IP address. The next time you start Twinkle, this IP address will be automatically selected. Die ausgewählte IP-Adresse als default setzen. In Zukunft verwendet Twinkle beim Start automatisch diese Adresse. Set as default &NIC A&nschl. als Standard Alt+N Alt+N Make the selected network interface the default interface. The next time you start Twinkle, this interface will be automatically selected. Den ausgewählten Netzwerkanschluss als default setzen. In Zukunft verwendet Twinkle beim Start automatisch diesen Anschluss. &OK Alt+O Alt+O If you want to remove or change the default at a later time, you can do that via the system settings. Die Defaulteinstellungen für IP bzw. Anschluss lassen sich jederzeit in den Systemeinstellungen löschen oder ändern. SelectProfileForm Twinkle - Select user profile Twinkle - Auswahl Benutzerprofil Select user profile(s) to run: Wählen Sie die Benutzerprofile, die verwendet werden sollen: User profile Benutzerprofil Tick the check boxes of the user profiles that you want to run and press run. Markieren Sie die Benutzerprofile, mit denen Twinkle arbeiten soll und drücken Sie dann "Anwenden". &New &Neu Alt+N Alt+N Create a new profile with the profile editor. Anlegen eines neuen Benutzerprofils mit dem Profil-Editor. &Wizard Alt+W Alt+W Create a new profile with the wizard. Anlegen eines neuen Benutzerprofils mit dem Wizard. &Edit Änd&ern Alt+E Alt+E Edit the highlighted profile. Das ausgewählte Benutzerprofil bearbeiten. &Delete &Löschen Alt+D Alt+L Delete the highlighted profile. Das ausgewählte Benutzerprofil löschen. Ren&ame &Umbenennen Alt+A Alt+U Rename the highlighted profile. Das ausgewählte Benutzerprofil umbenennen. &Set as default Als &Standard Alt+S Alt+S Make the selected profiles the default profiles. The next time you start Twinkle, these profiles will be automatically run. Die ausgewählten Profile als default verwenden. In Zukunft verwendet Twinkle beim Start automatisch diese Benutzerprofile. &Run &Anwenden Alt+R Alt+A Run Twinkle with the selected profiles. Twinkle startet die markierten Benutzerprofile. S&ystem settings S&ystemeinstellungen Alt+Y Alt+Y Edit the system settings. Systemeinstellungen bearbeiten. &Cancel Abbruch (Es&c) Alt+C Alt+C <html>Before you can use Twinkle, you must create a user profile.<br>Click OK to create a profile.</html> <html>Bevor Sie Twinkle benutzen können, müssen Sie ein Benutzerprofil anlegen.<br>Klicken Sie OK um ein neues Profil anzulegen.</html> <html>You can use the profile editor to create a profile. With the profile editor you can change many settings to tune the SIP protocol, RTP and many other things.<br><br>Alternatively you can use the wizard to quickly setup a user profile. The wizard asks you only a few essential settings. If you create a user profile with the wizard you can still edit the full profile with the profile editor at a later time.<br><br>Choose what method you wish to use.</html> <html>Sie können zum Erzeugen des Benutzerprofils den Profileditor verwenden. Dieser erlaubt detailierte Einstellungen für SIP-Protokoll, RTP sowie viele weitere Bereiche.<br><br>Oder Sie nutzen den Wizard, um schnell und einfach die wichtigsten Einstellungen für ein Benutzerprofil vorzunehmen. Der Wizard erfragt von Ihnen nur die absolut notwendigen Daten, die Sie von Ihrem SIP-Provider bei der Anmeldung mitgeteilt bekommen haben sollten. Für einige Provider werden Ihnen sogar viele dieser Daten vorgeschlagen. Wenn Sie den Wizard nutzen, können Sie später immer noch alle Details mit Hilfe des Editors nach Wunsch ändern und ergänzen.<br><br>Hilfe erhalten Sie überall in Twinkle entweder durch Drücken von "Umschalt + F1", über das Kontextmenü via rechten Mausknopf, oder durch Anklicken des "?" oben rechts am Fensterrand.<br><br>Bitte wählen Sie, wie Sie das Benutzerprofil anlegen wollen.</html> <html>Next you may adjust the system settings. You can change these settings always at a later time.<br><br>Click OK to view and adjust the system settings.</html> <html>Als nächstes können und sollten Sie die Systemeinstellungen kontrollieren und anpassen. Insbesondere die Einstellung zu Mikrophon und Lautsprecher im Bereich Audio sollte zu der in Ihrem Rechner vorhandenen Hardware passen<br><br>Klicken Sie OK um in die Systemeinstellungen zu gelangen. Sie können auch später jederzeit alle Systemeinstellungen ändern</html> You did not select any user profile to run. Please select a profile. Sie haben kein Benutzerprofil zur Verwendung ausgewählt. Bitte wählen Sie mindestens ein Profil. Are you sure you want to delete profile '%1'? Benutzerprofil %1 wirklich löschen? Delete profile Benutzerprofil löschen Failed to delete profile. Fehler beim Löschen des Benutzerprofils. Failed to rename profile. Fehler beim Umbenennen des Benutzerprofils. <p>If you want to remove or change the default at a later time, you can do that via the system settings.</p> <p>Die Defaulteinstellungen lassen sich jederzeit in den Systemeinstellungen löschen oder ändern. </p> Cannot find .twinkle directory in your home directory. Kann den Ordner ".twinkle" in Ihrem home-Ordner ("/home/ihrname/") nicht finden. &Profile editor &Profil-Editor Create profile Ed&itor Alt+I Alt+I Dia&mondcard Alt+M Modify profile Startup profile &Diamondcard Create a profile for a Diamondcard account. With a Diamondcard account you can make worldwide calls to regular and cell phones and send SMS messages. <html>You can use the profile editor to create a profile. With the profile editor you can change many settings to tune the SIP protocol, RTP and many other things.<br><br>Alternatively you can use the wizard to quickly setup a user profile. The wizard asks you only a few essential settings. If you create a user profile with the wizard you can still edit the full profile with the profile editor at a later time.<br><br> You can create a Diamondcard account to make worldwide calls to regular and cell phones and send SMS messages.<br><br> Choose what method you wish to use.</html> SelectUserForm Twinkle - Select user Twinkle - Auswahl Benutzerprofile &Cancel Abbruch (Es&c) Alt+C Alt+C &Select all Alle au&swählen Alt+S Alt+S &OK Alt+O Alt+O C&lear all Alle abwäh&len Alt+L Alt+L purpose No need to translate User Benutzer Register Anmelden Select users that you want to register. Benutzerprofile zum Anmelden wählen. Deregister Abmelden Select users that you want to deregister. Benutzerprofile zum Abmelden wählen. Deregister all devices Abmelden aller Geräte Select users for which you want to deregister all devices. Benutzerprofile zum Abmelden (alle Geräte) wählen. Do not disturb Bitte nicht stören Select users for which you want to enable 'do not disturb'. Benutzerprofile wählen, für die der Dienst "Bitte nicht stören" aktiviert werden soll. Auto answer Autom. Annehmen Select users for which you want to enable 'auto answer'. Benutzerprofile wählen, für die der Dienst "Automatisch Annehmen" aktiviert werden soll. SendFileForm Twinkle - Send File Twinkle - Sende Datei Select file to send. Dateiauswahl für Senden. &File: &Datei: &Subject: &Betreff: &OK &OK Alt+O Alt+O &Cancel Abbruch (Es&c) Alt+C Alt+C File does not exist. Datei existiert nicht. Send file... Sende Datei... SrvRedirectForm Twinkle - Call Redirection Twinkle - Rufumleitung User: Benutzer: There are 3 redirect services:<p> <b>Unconditional:</b> redirect all calls </p> <p> <b>Busy:</b> redirect a call if both lines are busy </p> <p> <b>No answer:</b> redirect a call when the no-answer timer expires </p> Es gibt 3 Arten von Rufumleitung:<p> <b>Immer:</b> alle Anrufe umleiten </p> <p> <b>Besetzt:</b> Anruf umleiten, wenn beide Leitungen besetzt </p> <p> <b>Keine Antwort:</b> Anruf nach Ablauf der "keine-Antwort"-Zeit umleiten </p> &Unconditional &Immer &Redirect all calls &Alle Anrufe umleiten Alt+R Alt+A Activate the unconditional redirection service. Den Dienst "alle Anrufe umleiten" aktivieren. Redirect to Umleiten nach You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. Es können bis zu 3 Ziele für die Rufumleitung angegeben werden. Wird der Ruf vom ersten Ziel nicht angenommen, werden das zweite und dann das dritte verwendet. &3rd choice destination: &3. Ziel: &2nd choice destination: &2. Ziel: &1st choice destination: &1. Ziel: Address book Adressbuch Select an address from the address book. Rufnummer/SIP-Adresse aus Adressbuch wählen. &Busy &Besetzt &Redirect calls when I am busy &Anrufe umleiten, wenn alle Leitungen besetzt Activate the redirection when busy service. Den Dienst "Umleiten, wenn besetzt" aktivieren. &No answer &keine Antwort &Redirect calls when I do not answer &Anrufe umleiten, wenn Benutzer nicht reagiert Activate the redirection on no answer service. Den Dienst "Umleiten, wenn keine Antwort" aktivieren. &OK Alt+O Alt+O Accept and save all changes. Änderungen speichern. &Cancel Abbruch (Es&c) Alt+C Alt+C Undo your changes and close the window. Änderungen verwerfen und Fenster schliessen. You have entered an invalid destination. Ungültige Zieladresse eingegeben. F10 F11 F12 SysSettingsForm Twinkle - System Settings Twinkle - Systemeinstellungen General Allgemein Audio Audio Ring tones Klingeltöne Address book Adressbuch Network Netzwerk Log Log Select a category for which you want to see or modify the settings. Bereich wählen, den Sie ändern wollen. Sound Card Audiogeräte Select the sound card for playing the ring tone for incoming calls. Audioanschluss wählen, über den der Klingelton abgespielt werden soll. Darf identisch mit Kopfhörer-Anschluss sein. Select the sound card to which your microphone is connected. Audioanschluss für Mikrofon wählen. Select the sound card for the speaker function during a call. Audioanschluss für Kopfhörer(/Lautsprecher) wählen. Darf identisch mit Anschluss für Klingelton sein. &Speaker: &Kopfhörer: &Ring tone: &Klingelton: Other device: Anderer Anschluss: &Microphone: &Mikrofon: When using ALSA, it is not recommended to use the default device for the microphone as it gives poor sound quality. Wenn Ihr Gesprächspartner schlechte Tonqualität beklagt, versuchen Sie für ALSA ein anderes Gerät statt "default". Reduce &noise from the microphone Spezielle Störgeräusch-U&nterdrückung für manche defekte Gateways Alt+N Alt+N Recordings from the microphone can contain noise. This could be annoying to the person on the other side of your call. This option removes soft noise coming from the microphone. The noise reduction algorithm is very simplistic. Sound is captured as 16 bits signed linear PCM samples. All samples between -50 and 50 are truncated to 0. Diese Option setzt alle Tonsamples mit -50 < Messwert < 50 auf 0. Michel hat diesen Hack entwickelt, als er mit fehlerhaften A/D-Wandlern in einigen Provider-Gateways konfrontiert war. Im Normalfall führt das Aktivieren eher zu einer kaum bemerkbaren Verschlechterung der Tonqualität. Advanced Spezielle Einstellungen OSS &fragment size: OSS &Fragmentgrösse: 16 32 64 128 256 The ALSA play period size influences the real time behaviour of your soundcard for playing sound. If your sound frequently drops while using ALSA, you might try a different value here. Die play period size bestimmt vereinfacht gesagt die Grösse der Pakete, in denen die Soundkarte die Daten geliefert bekommt. Viele kleine Pakete belasten den Prozessor mehr, aber der Verlust eines Pakets stört dann weniger. Bei Problemen mit Aussetzern oder Rattern im Ton sollten Sie hier mit anderen Werten ein wenig experimentieren. ALSA &play period size: ALSA &play (LS) period size: &ALSA capture period size: &ALSA capture (MIC) period size: The OSS fragment size influences the real time behaviour of your soundcard. If your sound frequently drops while using OSS, you might try a different value here. Die OSS period size bestimmt vereinfacht gesagt die Grösse der Pakete, in denen die Soundkarte die Daten geliefert bekommt/liefert. Viele kleine Pakete belasten den Prozessor mehr, aber der Verlust eines Pakets stört dann weniger. Bei Problemen mit Aussetzern oder Rattern im Ton sollten Sie hier mit anderen Werten ein wenig experimentieren. The ALSA capture period size influences the real time behaviour of your soundcard for capturing sound. If the other side of your call complains about frequently dropping sound, you might try a different value here. Die capture period size bestimmt vereinfacht gesagt die Grösse der Pakete, in denen die Soundkarte die Daten des Mikrofons liefert. Viele kleine Pakete belasten den Prozessor mehr, aber der Verlust eines Pakets stört dann weniger. Bei Klagen der Gegenstelle über Aussetzer oder Rattern im Ton sollten Sie hier mit anderen Werten ein wenig experimentieren. &Max log size: &Max. Grösse System-Log: The maximum size of a log file in MB. When the log file exceeds this size, a backup of the log file is created and the current log file is zapped. Only one backup log file will be kept. Das SystemLog wird nur von Experten zur Fehlersuche benötigt. Dieser Wert legt die maximale Grösse fest, die die Logbuch-Datei annehmen kann. Bei Erreichen dieser Grösse wird sie von twinkle.log in twinkle.log.old umbenannt, wobei eine schon existierende ältere Datei gleichen Namens gelöscht wird. Das Log wird in eine neue leere Datei twinkle.log fortgesetzt. Im Normalfall reicht hier 1MB völlig aus. MB Log &debug reports &Debug-Meldungen loggen Alt+D Alt+D Indicates if reports marked as "debug" will be logged. Aktiviert das Loggen von "debug"-Ausgaben. Log &SIP reports &SIP-Meldungen loggen Alt+S Alt+S Indicates if SIP messages will be logged. Aktiviert das Loggen von Ausgaben für SIP-Ereignisse. Log S&TUN reports S&TUN-Meldungen loggen Alt+T Alt+T Indicates if STUN messages will be logged. Aktiviert das Loggen von Ausgaben für STUN-Ereignisse. Log m&emory reports Sp&eicher-Meldungen loggen Alt+E Alt+E Indicates if reports concerning memory management will be logged. Aktiviert das Loggen von RAM-Anforderungs/Freigabe Protokollen. System tray Systemabschnitt Create &system tray icon on startup Bei &Start Icon in Systemabschnitt erzeugen Enable this option if you want a system tray icon for Twinkle. The system tray icon is created when you start Twinkle. Mit dieser Option erzeugt Twinkle beim Start ein Twinkle-Sternchen im Systemabschnitt , über das Sie jederzeit auf Twinkle zugreifen können und eingehende Rufe gemeldet bekommen. &Hide in system tray when closing main window Bei "Fenster sc&hliessen" in Systemabschnitt minimieren Alt+H Alt+H Enable this option if you want Twinkle to hide in the system tray when you close the main window. Wenn aktiviert, wird Twinkle durch Schliessen des Hauptfensters nicht beendet, sondern erzeugt das Sternchen im Systemabschnitt. Zum Beenden müssen Sie dann "Beenden" im Menü "Datei" oder im Kontextmenü des Sternchens wählen. Startup Programmstart Next time you start Twinkle, this IP address will be automatically selected. This is only useful when your computer has multiple and static IP addresses. Eine hier eingetragene IP-Adresse wird beim Start des Programms automatisch verwendet. Nur sinnvoll, wenn Ihr Rechner mehrere Netzwerkanschlüsse und für den Internetzugang eine unveränderliche IP-Adresse hat. Default &IP address: Default &IP-Addresse: Next time you start Twinkle, the IP address of this network interface be automatically selected. This is only useful when your computer has multiple network devices. Wenn Ihr Rechner mehrere Netzwerkanschlüsse hat, können Sie hier festlegen, welchen davon Twinkle beim Start verwenden soll. Sie werden dann nicht beim Start nach dem zu verwendenden Anschluss gefragt. Default &network interface: Default &Netzwerkanschluss: S&tartup hidden in system tray Minimiert im Sys&temabschnitt starten Next time you start Twinkle it will immediately hide in the system tray. This works best when you also select a default user profile. Twinkle öffnet beim Start kein Fenster, sondern startet minimiert im Systemabschnitt - also in Form des Sternchens. Dafür sollten Sie auch ein Default-Benutzerprofil einstellen, sonst erscheint doch ein Auswahlfenster beim Start. Default user profiles Default Benutzerprofile If you always use the same profile(s), then you can mark these profiles as default here. The next time you start Twinkle, you will not be asked to select which profiles to run. The default profiles will automatically run. Die hier eingestellten Benutzerprofile werden beim Programmstart automatisch aktiviert. Sie können trotzdem jederzeit über "Datei" "Benutzerprofile..." Profile aktivieren/deaktivieren. Services Dienste Call &waiting &Anklopfen Alt+W Alt+A With call waiting an incoming call is accepted when only one line is busy. When you disable call waiting an incoming call will be rejected when one line is busy. Wenn "Anklopfen" aktiviert ist, kann bei einer belegten Leitung über die zweite ein weiterer Anrufer klingeln. Wenn deaktiviert, bekommt er "besetzt". Hang up &both lines when ending a 3-way conference call. Bei Beenden 3er-Konferenz &beide Leitungen auflegen. Alt+B Alt+B Hang up both lines when you press bye to end a 3-way conference call. When this option is disabled, only the active line will be hung up and you can continue talking with the party on the other line. Wenn aktiviert, werden durch "Auflegen" beide Verbindungen getrennt. Sonst trennt "Auflegen" nur die aktive Leitung, das Gespräch auf der anderen Leitung kann weitergeführt werden. &Maximum calls in call history: &max. Anzahl Einträge in Anrufliste: The maximum number of calls that will be kept in the call history. Die Länge der Anrufliste wird auf die hier angegebene Anzahl Einträge begrenzt. Ältere Eintäge werden automatisch entfernt. &Auto show main window on incoming call after Bei &Anruf Hauptfenster öffnen nach Alt+A Alt+A When the main window is hidden, it will be automatically shown on an incoming call after the number of specified seconds. Wenn das Twinkle-Hauptfenster geschlossen oder minimiert ist, wird es bei eingehendem Ruf nach der angegebenen Sekundenzahl automatisch wiederhergestellt. Number of seconds after which the main window should be shown. Zeit in Sekunden, nach der das Fenster wiederhergestellt wird. secs Sekunden The UDP port used for sending and receiving SIP messages. Der UDP Port, über den das SIP-Protokoll läuft. Standard:5060, ihr Provider kann aber einen anderen Port vorschreiben. &RTP port: &RTP-Port: The UDP port used for sending and receiving RTP for the first line. The UDP port for the second line is 2 higher. E.g. if port 8000 is used for the first line, then the second line uses port 8002. When you use call transfer then the next even port (eg. 8004) is also used. Der erste UDP Port, über den das RTP-Protokoll zur Sprachdatenübertragung läuft. Ein zeitgleich geführtes 2. Gespräch nutzt einen um 2 höheren Port. Rufvermittlung weitere 2. Also beispielsweise: 1.Ltg:8000(+8001), 2.Ltg:8002(+8003), Vermitteln:8004(+8005). Standard: abhängig vom Provider meist 8000 oder 5004. Bei mehreren an einem Anschluss betriebenen SIP-fons braucht jedes seinen eigenen Bereich Ports! Also das 2. Twinkle z.B. dann 8006. &SIP UDP port: &SIP UDP Port: Ring tone Klingelton &Play ring tone on incoming call Bei eingeh. Ruf Klingelton s&pielen Alt+P Alt+P Indicates if a ring tone should be played when a call comes in. Wenn aktiviert, spielt Twinkle bei eingehenden Anrufen einen Klingelton über den dafür eingestellten Audioanschluss ab. &Default ring tone &Default Klingelton Play the default ring tone when a call comes in. Spielt den Standard-Klingelton, wenn Anruf ankommt. C&ustom ring tone individueller &Ton Alt+U Alt+T Play a custom ring tone when a call comes in. Selbst ausgewählten Klingelton abspielen bei eingehendem Anruf. Specify the file name of a .wav file that you want to be played as ring tone. Geben Sie hier den Namen der .wav-Datei für Ihren individuellen Klingelton an. Ring back tone Freizeichen P&lay ring back tone when network does not play ring back tone Freizeichen (Rufton) abspie&len, wenn Tel-netz keinen liefert Alt+L Alt+L <p> Play ring back tone while you are waiting for the far-end to answer your call. </p> <p> Depending on your SIP provider the network might provide ring back tone or an announcement. </p> <p>Freizeichen abspielen, wenn die Telefongesellschaft nicht selber eines einspielt. </p> <p>Das Freizeichen ist in D das "tuuut tuuut"", welches dem Anrufer das Klingeln beim Gerufenen anzeigt. </p> <p>Twinkle spielt dann den eigenen "call back tone" ab, falls nicht eine der beteiligten Vermittlungsstellen selber einen entsprechenden Signalton oder eine Ansage liefert.</p> D&efault ring back tone D&efault Freizeichen Play the default ring back tone. Den Standard-Signalton für Rückmeldung des Klingelns bein Angerufenen (=Freizeichen) verwenden. Cu&stom ring back tone Individuelle&s Freizeichen Play a custom ring back tone. Individuellen Signalton für Rückmeldung des Klingelns bein Angerufenen (=Freizeichen) verwenden. Specify the file name of a .wav file that you want to be played as ring back tone. Geben Sie hier den Namen der .wav-Datei für Ihr individuelles Freizeichen an. &Lookup name for incoming call Zu Nummer der Gegenstelle Name ermitte&ln Ove&rride received display name Gemeldeten An&rufernamen ersetzen Alt+R Alt+R The caller may have provided a display name already. Tick this box if you want to override that name with the name you have in your address book. Die Gegenstelle kann selber einen Namen (displayname) mitschicken. Aktivieren sie diese Option, wenn Sie lieber den aus der Nummer/Adresse ermittelten Eintrag aus Ihrem Adressbuch angezeigt bekommen möchten. Lookup &photo for incoming call Nach &Foto für Gegenstelle suchen Lookup the photo of a caller in your address book and display it on an incoming call. Wenn aufgrund der Nummer/Adresse der Gegenstelle ein Datensatz in Ihrem Adressbuch gefunden wird, zeigt Twinkle ein dort gegebenenfalls hinterlegtes Foto an. &OK Alt+O Alt+O Accept and save your changes. Änderungen übernehmen und speichern. &Cancel Abbruch (Es&c) Alt+C Alt+C Undo all your changes and close the window. Fenster schliessen ohne Änderungen zu übernehmen. none This is the 'none' in default IP address combo auto none This is the 'none' in default network interface combo auto Either choose a default IP address or a default network interface. Sie dürfen nur entweder einen Default-Anschluss oder eine Default-IP-Adresse angeben. Ring tones Description of .wav files in file dialog Klingeltöne Choose ring tone Auswahl Klingelton Ring back tones Description of .wav files in file dialog Signaltöne Choose ring back tone Auswahl Freizeichen &Validate devices before usage Audioeinstellungen prüfen &vor Benutzung Alt+V Alt+V <p> Twinkle validates the audio devices before usage to avoid an established call without an audio channel. <p> On startup of Twinkle a warning is given if an audio device is inaccessible. <p> If before making a call, the microphone or speaker appears to be invalid, a warning is given and no call can be made. <p> If before answering a call, the microphone or speaker appears to be invalid, a warning is given and the call will not be answered. <p>Wenn aktiviert, prüft Twinkle die eingestellten Audiodevices, um zu verhindern dass eine Verbindung ohne entsprechende Ton-Ein/Ausgabe aufgebaut wird.</p> <p>Bein Programmstart warnt Twinkle, falls eines der Audiodevices nicht verfügbar ist.<br> Bei Anrufen werden Mikrofon- und Lautsprecherdevice geprüft.</p> <p>Versuche, einen abgehenden Ruf zu tätigen, werden bei gefundenen Audio-Problemen abgebrochen,<br> eingehende Rufe werden nicht entgegengenommen. <br> Stattdessen zeigt Twinkle in beiden Fällen eine Warnung.</p> On an incoming call, Twinkle will try to find the name belonging to the incoming SIP address in your address book. This name will be displayed. Twinkle versucht, einen zur Nummer/Adresse der Gegenstelle passenden Eintrag im Adressbuch zu finden. Die Details dieses Eintrags werden dann angezeigt. Select ring tone file. Dateiauswahl Klingelton. Select ring back tone file. Dateiauswahl Freizeichen. Maximum allowed size (0-65535) in bytes of an incoming SIP message over UDP. Max. zulässige Größe in Byte (0-65535) für ankommende SIP-Nachrichten über UDP. &SIP port: &SIP port: Max. SIP message size (&TCP): Max. SIP-Nachrichtengröße (&TCP): The UDP/TCP port used for sending and receiving SIP messages. Die Portnummer, über die SIP-Nachrichten sowohl per UDP als auch TCP gesendet und empfangen werden. Max. SIP message size (&UDP): Max. SIP-Nachrichtengröße (&UDP): Maximum allowed size (0-4294967295) in bytes of an incoming SIP message over TCP. Max. zulässige Größe in Byte (0-4294967295) für ankommende SIP-Nachrichten über TCP. W&eb browser command: Command to start your web browser. If you leave this field empty Twinkle will try to figure out your default web browser. 512 1024 Tip: for crackling sound with PulseAudio, set play period size to maximum. Enable in-call OSD SysTrayPopup Answer Annehmen Reject Abweisen Incoming Call TermCapForm Twinkle - Terminal Capabilities Twinkle - Fähigkeiten Gegenstelle &From: &Von: Get terminal capabilities of Fähigkeiten folgender Gegenstelle abfragen &To: &Adr: The address that you want to query for capabilities (OPTION request). This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Die Adresse/Nummer der Gegenstelle, deren Fähigkeiten Sie erfragen möchten (OPTION request). Wie immer bei Twinkle kann dies eine vollständige Adresse oder ein Username sein. Address book Adressbuch Select an address from the address book. Rufnummer/SIP-Adresse aus Adressbuch wählen. &OK &Cancel Abbruch (Es&c) F10 TransferForm Twinkle - Transfer Twinkle - Vermitteln Transfer call to Ruf weitervermitteln an &To: &Adr: The address of the person you want to transfer the call to. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Die Adresse/Nummer der Gegenstelle, an die Sie weitervermitteln möchten. Wie immer bei Twinkle kann dies eine vollständige Adresse oder ein Username sein. Address book Adressbuch Select an address from the address book. Rufnummer/SIP-Adresse aus Adressbuch wählen. &OK Alt+O Alt+O &Cancel Abbruch (Es&c) Type of transfer Art der Vermittlung &Blind transfer &Ohne Rücksprache Alt+B Alt+O Transfer the call to a third party without contacting that third party yourself. Das Gespräch wird an den dritten, neuen Teilnehmer umgelenkt, ohne dass Sie vorher mit diesem Rücksprache halten. D.h. wenn der neue Teilnehmer abhebt, ist er sofort mit Ihrem bisherigen Gesprächspartner verbunden. T&ransfer with consultation Mit &Rücksprache Alt+R Alt+R Before transferring the call to a third party, first consult the party yourself. Sie können mit dem neuen Teilnehmer sprechen und den vermittelten Gesprächspartner ankündigen. Nach Ende dieser Rücksprache wird Ihr bisheriger Gesprächspartner mit der neuen Gegenstelle verbunden. Transfer to other &line Vermitteln an andere &Leitung Alt+L Alt+L Connect the remote party on the active line with the remote party on the other line. Die beiden GgSt an Leitung 1 und 2 zueinander vermitteln. Hierbei ist die GgSt der gerade aktiven Ltg die vermittelte, also den Ruf aufbauende. F10 TwinkleCore Failed to create log file %1 . Fehler beim Anlegen der Logdatei "%1". Cannot open file for reading: %1 Kann Datei "%1" nicht zum Lesen öffnen File system error while reading file %1 . Dateisystem-Fehler beim Lesen aus "%1". Cannot open file for writing: %1 Kann Datei "%1" nicht zum Schreiben öffnen File system error while writing file %1 . Dateisystem-Fehler beim Schreiben in "%1". Excessive number of socket errors. Zu hohe Anzahl von socket-Fehlern. Built with support for: Erstellt mit Unterstützung für: Contributions: Beiträge: This software contains the following software from 3rd parties: Diese Software enthält folgende Teile Dritter: * GSM codec from Jutta Degener and Carsten Bormann, University of Berlin * G.711/G.726 codecs from Sun Microsystems (public domain) * iLBC implementation from RFC 3951 (www.ilbcfreeware.org) * Parts of the STUN project at http://sourceforge.net/projects/stun * Parts of libsrv at http://libsrv.sourceforge.net/ For RTP the following dynamic libraries are linked: Translated to english by <your name> Deutsche Übersetzung: ©left 20080810-0830 Reisenweber tech+it-consult<br> joerg.twinklephone(AT)gmx.de Directory %1 does not exist. Ordner "%1" nicht gefunden. Cannot open file %1 . Datei "%1" nicht zugreifbar. (nicht vorhanden / schreibgeschützt?). %1 is not set to your home directory. "%1" zeigt nicht auf Ihren home-Ordner. Directory %1 (%2) does not exist. Ordner "%1" (%2) existiert nicht. Cannot create directory %1 . Ordner "%1" kann nicht erstellt werden. Lock file %1 already exist, but cannot be opened. Sperrdatei "%1" existiert schon, kann aber nicht geöffnet werden. %1 is already running. Lock file %2 already exists. "%1" ist offenbar schon gestartet. Sperrdatei "%2" existiert schon. Cannot create %1 . "%1" kann nicht angelegt werden. Cannot write to %1 . Kann in "%1" nicht schreiben. Syntax error in file %1 . Syntaktische Struktur in Datei "%1" fehlerhaft. Failed to backup %1 to %2 Fehler beim Backup von "%1" nach "%2" unknown name (device is busy) Gerät unbekannt oder schon belegt Default device Standard Anschluss Anonymous Anonym Warning: Warnung: Call transfer - %1 Vermittlung - %1 Sound card cannot be set to full duplex. Audiodevice kann nicht auf "voll duplex" eingestellt werden. Cannot set buffer size on sound card. Puffergrösse f. Audiodevice kann nicht eingestellt werden. Sound card cannot be set to %1 channels. Audiodevice kann nicht auf %1 Kanäle eingestellt werden. Cannot set sound card to 16 bits recording. Audiodevice kann nicht auf 16Bit-Aufnahme eingestellt werden. Cannot set sound card to 16 bits playing. Audiodevice kann nicht auf 16Bit-Wiedergabe eingestellt werden. Cannot set sound card sample rate to %1 Audio Samplerate kann nicht auf %1 eingestellt werden. Opening ALSA driver failed Fehler beim Öffnen des ALSA-Treibers Cannot open ALSA driver for PCM playback ALSA-Treiber kann nicht f. PCM-Wiederg. geöffnet werden. Cannot resolve STUN server: %1 Kann URL d. STUN-Servers nicht auflösen: %1 You are behind a symmetric NAT. STUN will not work. Configure a public IP address in the user profile and create the following static bindings (UDP) in your NAT. Sie befinden sich hinter einer "symetric NAT". STUN kann hier nicht funktionieren. Sie müssen in Twinkles Benutzerprofil/NAT eine "fest voreingestellte Adresse" einstellen. In Ihrem Router/Firewall/NAT leiten Sie bitte folgende öffentliche Ports auf lokale Ports zum Twinkle-PC weiter: public IP: %1 --> private IP: %2 (SIP signaling) IP öffentl.: %1 --> IP lokal: %2 (SIP Protokoll) public IP: %1-%2 --> private IP: %3-%4 (RTP/RTCP) IP öff.: %1 - %2 --> IP lok.: %3 - %4 (RTP/RTCP) Cannot reach the STUN server: %1 Kann STUN-Server "%1" nicht erreichen. Port %1 (SIP signaling) Port %1 (SIP Protokoll) NAT type discovery via STUN failed. NAT Analyse mittels STUN fehlgeschlagen. If you are behind a firewall then you need to open the following UDP ports. Wenn Sie sich hinter einer Firewall befinden, müssen Sie folgende Ports öffnen: Ports %1-%2 (RTP/RTCP) Ports %1-%2 (RTP/RTCP) Cannot access the ring tone device (%1). "%1", Audiodevice f. Klingelton nicht zugreifbar. Cannot access the speaker (%1). "%1", Audiodevice f. Lautsprecher nicht zugreifbar. Cannot access the microphone (%1). "%1", Audiodevice f. Mikrofon nicht zugreifbar. Cannot open ALSA driver for PCM capture ALSA-Treiber kann nicht für PCM-Aufnahme geöffnet werden Cannot receive incoming TCP connections. Kann eingehende TCP-Verbindungen nicht annehmen. Failed to create file %1 Fehler beim Anlegen der Datei "%1" Failed to write data to file %1 Fehler beim Schreiben in Datei "%1" Failed to send message. Fehler beim Senden der Nachricht. Cannot lock %1 . UserProfileForm Twinkle - User Profile Twinkle - Benutzerprofil User profile: Benutzerprofil: Select which profile you want to edit. Zu bearbeitendes Benutzerprofil wählen. User Benutzer SIP server SIP Server RTP audio RTP Audio SIP protocol SIP-Protokoll Address format Adress-Format Timers Zeitgeber Ring tones Signaltöne Scripts Security Sicherheit Select a category for which you want to see or modify the settings. Bereich wählen, den Sie ändern wollen. &OK Alt+O Alt+O Accept and save your changes. Änderungen übernehmen und speichern. &Cancel Abbruch (Es&c) Alt+C Alt+C Undo all your changes and close the window. Fenster schliessen ohne Änderungen zu übernehmen. SIP account SIP-Provider Benutzerdaten &User name*: N&utzername *: &Domain*: Or&ganization: Or&ganisation: The SIP user name given to you by your provider. It is the user part in your SIP address, <b>username</b>@domain.com This could be a telephone number. <br><br> This field is mandatory. Der Nutzername, den Sie von Ihrem Provider zugewiesen bekommen haben. Dieser ist der erste Teil ihrer vollständigen SIP-Adresse <b>nutzername</b>@domain.com . Viele Provider bezeichnen diesen -eigentlich falsch- als Telefonnummer. <br><br> *DATEN FÜR DIESES FELD SIND ZWINGEND NOTWENDIG. The domain part of your SIP address, username@<b>domain.com</b>. Instead of a real domain this could also be the hostname or IP address of your <b>SIP proxy</b>. If you want direct IP phone to IP phone communications then you fill in the hostname or IP address of your computer. <br><br> This field is mandatory. Die Domain oder IP-Adresse, unter der Sie von Ihrem Provider geführt werden bzw. im Internet erreichbar sind. Dies ist der zweite Teil ihrer vollständigen SIP-Adresse nutzername@<b>domain.com</b>, bzw. die Domain Ihres SIP-Proxys. Bei vielen Providern identisch mit der Domain des Providers. Für direct-IP-to-IP (siehe Handbuch) ist hier die Adresse (DynDNS oder IP) einzutragen, unter der <b>Ihr Rechner</b> zu erreichen ist. <br><br> *DATEN FÜR DIESES FELD SIND ZWINGEND NOTWENDIG. You may fill in the name of your organization. When you make a call, this might be shown to the called party. In deutsch etwa Firma. Dieses Feld wird nur als Teil der Absenderangaben zur angerufenen/rufenden Gegenstelle übertragen und dort evtl angezeigt. Beliebige Angabe, nicht zwingend erforderlich. Vermeiden Sie moeglichst Umlaute und Sonderzeichen, manche Gegenstellen haben damit Probleme. This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. Ihr Absendername oder Pseudonym. Dieses Feld wird nur als Teil der Absenderangaben (display name) zur angerufenen/rufenden Gegernstelle übertragen und dort evtl angezeigt. Beliebige Angabe, nicht zwingend erforderlich. Vermeiden Sie moeglichst Umlaute und Sonderzeichen, manche Gegenstellen haben damit Probleme. &Your name: &Absender: SIP authentication SIP-Anmeldedaten &Realm: Authentication &name: Anmelde&name: &Password: &Passwort: The realm for authentication. This value must be provided by your SIP provider. If you leave this field empty, then Twinkle will try the user name and password for any realm that it will be challenged with. Der "Realm"-Wert (deutsch etwa: Bereich) zur Anmeldung. Wird Ihnen, falls notwendig, gegebenfalls von Ihrem SIP-Provider mitgeteilt. Wenn leer, verwendet Twinkle SIP-Anmeldename und Passwort bei jeder Realm-Anfrage. Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Ihr SIP-Anmeldename. Häufig identisch mit Ihrem SIP-Nutzernamen, dann leerlassen. Falls nicht, wird Ihr Provider dies mitteilen. Your password for authentication. Ihr SIP-Anmeldepasswort. Wenn Sie dieses Feld leerlassen, müssen Sie das Passwort bei jeder Anmeldung in den dann erscheinenden Requester eintragen (hilfreich zum anfänglichen Testen!). Registrar Registrar (Anmelde-Server) &Registrar: &Registrar: The hostname, domain name or IP address of your registrar. If you use an outbound proxy that is the same as your registrar, then you may leave this field empty and only fill in the address of the outbound proxy. Die Domain, IP oder Hostname Ihres SIP-Anmelde-Servers. Für die meisten SIP-Provider einfach leer lassen. Wenn unten ein Outbound-Proxy eingetragen ist, wird dieser bei leerem Feld auch hier verwendet. Ohne Outbound-Proxy gilt für beides die Benutzer-SIP-Domain. &Expiry: &haltbar: The registration expiry time that Twinkle will request. Die Gültigkeitsdauer in Sekunden, die Twinkle bei der Anmeldung anfordert. Nach dieser Zeit meldet sich Twinkle automatisch neu an. Unterbleibt dies, bemerkt der Provider nach dieser Zeit, dass Sie offline sind. Auch Änderungen Ihrer IP -z.B. durch Zwangstrennung- werden u.U. erst nach dieser Zeit berücksichtigt. Werte kleiner 120 sind nicht zu empfehlen. Standard: 3600 (=1h). seconds Sekunden Re&gister at startup Bei &Profilstart anmelden Alt+G Alt+P Indicates if Twinkle should automatically register when you run this user profile. You should disable this when you want to do direct IP phone to IP phone communication without a SIP proxy. Wenn aktiviert, versucht Twinkle, dieses Benutzerprofil bei seiner Aktivierung automatisch beim Provider (genauer SIP-Anmelde-Server / Registrar) anzumelden. Für direct-IP-to-IP gibt es keinen Provider, also dann nicht aktivieren. Outbound Proxy &Use outbound proxy Outbound-Proxy ben&utzen Alt+U Alt+U Indicates if Twinkle should use an outbound proxy. If an outbound proxy is used then all SIP requests are sent to this proxy. Without an outbound proxy, Twinkle will try to resolve the SIP address that you type for a call invitation for example to an IP address and send the SIP request there. Wenn aktiviert, verwendet Twinkle einen Outbound-Proxy (deutsch etwa: Stellvertreter/Vermittlung für abgehende Rufe), an den alle SIP-Anfragen gesendet werden. Dies kann z.B. ein SIP-Gateway ihres Firmen-LAN sein. Ohne Outbound-Proxy (Normalfall) versucht Twinkle selbst, die zu rufende Adresse zu einer IP aufzulösen, und sendet die SIP-Anfrage für den Anruf direkt dorthin. Outbound &proxy: Outbound-&Proxy: &Send in-dialog requests to proxy In-Dialog-Anfragen an Proxy &senden Alt+S Alt+S SIP requests within a SIP dialog are normally sent to the address in the contact-headers exchanged during call setup. If you tick this box, that address is ignored and in-dialog request are also sent to the outbound proxy. Wenn aktiviert, SIP-Anfragen <b>immer</b> an den Outbound-Proxy senden. Twinkle sendet normalerweise SIP-Anfragen während eines laufenden SIP-Dialogs (d.h. während eines Gesprächs) an die Adresse im zu Gesprächsbeginn erhaltenen Contact-Header, also direkt an die Gegenstelle. &Don't send a request to proxy if its destination can be resolved locally. SIP-Anfragen mit lokal auflösbarer A&dresse nicht an Proxy, sondern direkt senden. Alt+D Alt+D When you tick this option Twinkle will first try to resolve a SIP address to an IP address itself. If it can, then the SIP request will be sent there. Only when it cannot resolve the address, it will send the SIP request to the proxy (note that an in-dialog request will only be sent to the proxy in this case when you also ticked the previous option.) Wenn aktiviert, versucht Twinkle zunächst selbst, die Zieladresse zu einer gültigen IP-Adresse aufzulösen und die SIP-Anfrage direkt dorthin zu schicken. Gelingt die Adressauflösung nicht, wird die Anfrage trotzdem an den Proxy geschickt, wie bei nicht aktivierter Option (Beachten Sie: In-Dialog-Anfragen werden in diesem Fall nur an den Proxy gesendet, wenn auch die vorherige Option aktiviert ist) The hostname, domain name or IP address of your outbound proxy. Der Domainname, IP-Adresse oder Hostname Ihres Outbound-Proxy. Co&decs Codecs Available codecs: Verfügbare Codecs: G.711 A-law G.711 u-law GSM speex-nb (8 kHz) speex-wb (16 kHz) speex-uwb (32 kHz) List of available codecs. Liste der verfügbaren, nicht aktivierten Codecs. Abhängig von den Compile-options können manche Codecs nicht verfügbar sein. Move a codec from the list of available codecs to the list of active codecs. Codec aktivieren. Move a codec from the list of active codecs to the list of available codecs. Codec deaktivieren. Active codecs: Aktive Codecs: List of active codecs. These are the codecs that will be used for media negotiation during call setup. The order of the codecs is the order of preference of use. Liste der aktiven Codecs. Diese werden beim Gesprächsaufbau der Gegenstelle zur Benutzung angeboten bzw. akzeptiert. Es wird bevorzugt der am weitesten oben in der Liste stehende Codec genutzt, auf den sich die beiden Endgeräte einigen können. Move a codec upwards in the list of active codecs, i.e. increase its preference of use. Codec in der Liste nach oben verschieben, d.h. höheren Vorrang für Benutzung einräumen. Move a codec downwards in the list of active codecs, i.e. decrease its preference of use. Codec in der Liste nach unten verschieben, d.h. niedrigeren Vorrang für Benutzung einräumen. &G.711/G.726 payload size: &G.711/G.726 Nutzdatengrösse: The preferred payload size for the G.711 and G.726 codecs. Die bevorzugte Grösse der Nutzdaten pro RTP-Paket für G.711 and G.726 Codecs. ms &iLBC iLBC i&LBC payload type: i&LBC Nutzdaten-Typ: iLBC &payload size (ms): iLBC &Nutzdatengrösse: The dynamic type value (96 or higher) to be used for iLBC. Die für iLBC verwendete dynamische Nutzdatentypkennung (nicht kleiner 96). 20 30 The preferred payload size for iLBC. Die bevorzugte Grösse der Nutzdaten pro RTP-Paket für iLBC. &Speex Speex Perceptual &enhancement Tonqualität v&erbessern Alt+E Alt+E Perceptual enhancement is a part of the decoder which, when turned on, tries to reduce (the perception of) the noise produced by the coding/decoding process. In most cases, perceptual enhancement make the sound further from the original objectively (if you use SNR), but in the end it still sounds better (subjective improvement). "Tonqualität verbessern" (engl: perceptual enhancement) ist eine Sammlung von Funktionen des Codecs, die den Ton unter Beachtung der Eigenschaften des menschlichen Hörens so bearbeiten, dass weniger Störgeräusche wahrgenommen werden. Obwohl sich die Übertragung bei Anwendung dieser Funktionen unter messtechnischen Gesichtspunkten (S/N Rauschabstand) verschlechtert und weniger dem Original gleicht, ist letztendlich doch die empfundene Tonqualität besser. &Ultra wide band payload type: &Ultra wide band Nutzdaten-Typ: Alt+V Alt+V When enabled, voice activity detection detects whether the audio being encoded is speech or silence/background noise. VAD is always implicitly activated when encoding in VBR, so the option is only useful in non-VBR operation. In this case, Speex detects non-speech periods and encode them with just enough bits to reproduce the background noise. This is called "comfort noise generation" (CNG). Wenn aktiviert, prüft VAD (Voice Activity Detection, deutsch etwa: Sprache/Pause-Erkennung), ob gerade gesprochen wird. Nicht als Sprache erkannte Geräusche werden nicht übertragen, sondern es wird stattdessen ein wesentlich weniger Daten-Bandbreite benötigendes "Pausesignal" oder (siehe DTX) gar nichts gesendet. VBR (siehe dort) macht VAD unnötig. &Wide band payload type: &wide band Nutzdaten-Kennung: Alt+B Alt+B Variable bit-rate (VBR) allows a codec to change its bit-rate dynamically to adapt to the "difficulty" of the audio being encoded. In the example of Speex, sounds like vowels and high-energy transients require a higher bit-rate to achieve good quality, while fricatives (e.g. s,f sounds) can be coded adequately with less bits. For this reason, VBR can achieve a lower bit-rate for the same quality, or a better quality for a certain bit-rate. Despite its advantages, VBR has two main drawbacks: first, by only specifying quality, there's no guarantee about the final average bit-rate. Second, for some real-time applications like voice over IP (VoIP), what counts is the maximum bit-rate, which must be low enough for the communication channel. Variable Bit-Rate (VBR) erlaubt es dem Codec, die Menge der übertragenen Daten entsprechend der Komplexität des Audiosignals anzupassen. Zischlaute wie "s", "f" z.B. und besonders Sprechpausen (siehe VAD) können mit wenigen Daten qualitativ gut beschrieben werden, während für Laute mit starken Änderungen im zeitlichen Verlauf ("p", "k", "r"...) vergleichsweise hohe Datenmengen nötig sind. Durch VBR kann bei gegebener Datenrate also insgesamt bessere Tonqualität erreicht werden, oder niedrigere Datenraten für gleiche Qualität. Allerdings ist bei Festlegung einer bestimmten einzuhaltenden Qualität nicht mehr vorhersagbar, welche Datenrate dafür ausreichend sein wird. Bei Echtzeitanwendungen wie VoIP ist aber gerade die maximal benötigte und nicht die durchschnittliche Datenrate kritisch. The dynamic type value (96 or higher) to be used for speex wide band. Die für speex wide band verwendete dynamische Nutzdatentyp-Kennung (nicht kleine 96). Co&mplexity: Ko&mplexität: Alt+X Alt+X Discontinuous transmission is an addition to VAD/VBR operation, that allows one to stop transmitting completely when the background noise is stationary. Discontinuous transmission (deutsch etwa: nicht kontinuierliche Datenübertragung) ist eine Erweiterung der VAD/VBR-Übertragung. Bei gleichbleibenden Audiosignal (insbesondere bei erkannten Sprechpausen) wird statt ständig der gleichen Nutzdaten einfach gar nichts übertragen. Senkt die durchschnittliche Datenrate etwas. Bei Störungen auf dem Übertragungsweg kann diese Option zu den von Mobiltelefonen der Anfangszeit bekannten absurden Tonstörungen (hängenbleiben des Tons, Artefakte) führen. The dynamic type value (96 or higher) to be used for speex narrow band. Die für speex narrow band verwendete dynamische Nutzdatentyp-Kennung (nicht kleiner 96). With Speex, it is possible to vary the complexity allowed for the encoder. This is done by controlling how the search is performed with an integer ranging from 1 to 10 in a way that's similar to the -1 to -9 options to gzip and bzip2 compression utilities. For normal use, the noise level at complexity 1 is between 1 and 2 dB higher than at complexity 10, but the CPU requirements for complexity 10 is about 5 times higher than for complexity 1. In practice, the best trade-off is between complexity 2 and 4, though higher settings are often useful when encoding non-speech sounds like DTMF tones. Bei Speex kann die Komplexität (=Genauigkeit) festgelegt werden, mit der der Codec arbeitet. Hierzu wird die Tiefe des Suchvorgangs mit einem Wert von 1 bis 10 gesteuert, ähnlich der -1 bis -9 Option von gzip und bzip2. Im Normalbetrieb ist bei 1 der Rauschabstand 1 bis 2dB schlechter und die CPU-Auslastung nur 10-20% im Vergleich zu 10. In der Praxis bewährt sich für Sprache eine Einstellung von 2 - 4, Inband-DTMF z.B. und andere technische Signale, oder auch Musik, profitieren u.U. von höheren Einstellungen. &Narrow band payload type: &Narrow band Nutzdatentyp-Kennung: G.726 G.726 &40 kbps payload type: G.726 &40 kb/s Nutzdatentyp-Kennung: The dynamic type value (96 or higher) to be used for G.726 40 kbps. Die für G.726 40 kb/s verwendete dynamische Nutzdatentypkennung (nicht kleiner 96). The dynamic type value (96 or higher) to be used for G.726 32 kbps. Die für G.726 32 kb/s verwendete dynamische Nutzdatentypkennung (nicht kleiner 96). G.726 &24 kbps payload type: G.726 &24 kb/s Nutzdatentyp-Kennung: The dynamic type value (96 or higher) to be used for G.726 24 kbps. Die für G.726 24 kb/s verwendete dynamische Nutzdatentypkennung (nicht kleiner 96). G.726 &32 kbps payload type: G.726 &32 kb/s Nutzdatentyp-Kennung: The dynamic type value (96 or higher) to be used for G.726 16 kbps. Die für G.726 16 kb/s verwendete dynamische Nutzdatentypkennung (nicht kleiner 96). G.726 &16 kbps payload type: G.726 &16 kb/s Nutzdatentyp-Kennung: DT&MF DTMF The dynamic type value (96 or higher) to be used for DTMF events (RFC 2833). Die für DTMF (RFC2833) verwendete dynamische Nutzdatentypkennung (nicht kleiner 96). DTMF vo&lume: DTMF &Lautstärke: The power level of the DTMF tone in dB. Die Lautstärke der gesendeten DTMF-Töne in dB, sowohl für reale Töne inband als auch Pegelkennung bei RFC2833. Sollte -10 bis -6 sein. The pause after a DTMF tone. Dauer der Pause zwischen 2 DTMF-Tönen. Zu kleine Werte können dazu führen, dass Folgen von gleichen "Ziffern" vom gesteuerten Gerät nicht mehr getrennt und als nur eine erkannt werden. Hohe Werte sind unschädlich, sofern Sie es nicht eilig haben. DTMF &duration: DTMF-&Dauer: DTMF payload &type: D&TMF Nutzdatentyp-Kennung: DTMF &pause: DTMF-&Pause: dB Duration of a DTMF tone. Dauer eines DTMF-Tons in Millisekunden. Bei zu kleinem Wert kann das gesteuerte Gerät die "Ziffer" nicht mehr erkennen. 200 klappt meist auch mit alten Geräten. DTMF t&ransport: DTMF &Methode: Auto RFC 2833 Inband Out-of-band (SIP INFO) <h2>RFC 2833</h2> <p>Send DTMF tones as RFC 2833 telephone events.</p> <h2>Inband</h2> <p>Send DTMF inband.</p> <h2>Auto</h2> <p>If the far end of your call supports RFC 2833, then a DTMF tone will be send as RFC 2833 telephone event, otherwise it will be sent inband. </p> <h2>Out-of-band (SIP INFO)</h2> <p> Send DTMF out-of-band via a SIP INFO request. </p> <p><h3>RFC 2833</h3> Sende DTMF-Töne als RFC 2833 telephone events (Symbole im RTP-Audiodatenstrom).</p> <p><h3>Inband</h3> Sende DTMF inband (tatsächliche Töne, die Twinkle ins Tonsignal einmischt).</p> <p><h3>Auto</h3> Wenn die Gegenstelle RFC 2833 unterstützt, dann DTMF-Töne als RFC 2833 telephone events senden, ansonsten inband.</p> <p><h3>Out-of-band (SIP INFO)</h3> Sende DTMF nur out-of-band via SIP INFO request.</p> General Allgemein Redirection Rufweiterleitung abgehende Rufe &Allow redirection Rufweiterleitung erl&auben Alt+A Alt+A Indicates if Twinkle should redirect a request if a 3XX response is received. Wenn aktiviert, befolgt Twinkle die Anforderung (3XX) der gerufenen Gegenstelle, wenn dort Rufumleitung aktiviert wurde. Ask user &permission to redirect Benutzer vor &Weiterleitung fragen Alt+P Alt+W Indicates if Twinkle should ask the user before redirecting a request when a 3XX response is received. Wenn aktiviert, fragt Twinkle bei Empfang einer 3XX-Anfrage, ob der abgehende Ruf auf ein alternatves Ziel umgeleitet werden darf. Max re&directions: Max. Anz. &Umleit.: The number of redirect addresses that Twinkle tries at a maximum before it gives up redirecting a request. This prevents a request from getting redirected forever. Die Anzahl von Weiterleitungen eines abgehenden Rufes (von A nach B nach C...), nach der Twinkle aufgibt. Verhindert Endlosweiterleitungen im Kreis (A -> B -> A...). Protocol options Protokoll-Optionen Call &Hold variant: Gespräch-&halten Variante: RFC 2543 RFC 3264 Indicates if RFC 2543 (set media IP address in SDP to 0.0.0.0) or RFC 3264 (use direction attributes in SDP) is used to put a call on-hold. Auswahl, ob RFC 2543 (set media IP address in SDP to 0.0.0.0) oder RFC 3264 (use direction attributes in SDP) benutzt wird, um ein Gespräch zu halten. Allow m&issing Contact header in 200 OK on REGISTER Erlaube fehlenden Contact header in 200 OK bei REG&ISTER Alt+I Alt+I A 200 OK response on a REGISTER request must contain a Contact header. Some registrars however, do not include a Contact header or include a wrong Contact header. This option allows for such a deviation from the specs. Eine "200 OK"-Antwort auf ein "REGISTER" muss einen Contact header enthalten. Einige Provider schicken trotzdem keinen oder einen fehlerhaften. Wenn aktiviert, wird Twinkle versuchen, diesen Fehler auszugleichen. &Max-Forwards header is mandatory &Max-Forwards-Header verlangen Alt+M Alt+M According to RFC 3261 the Max-Forwards header is mandatory. But many implementations do not send this header. If you tick this box, Twinkle will reject a SIP request if Max-Forwards is missing. Nach RFC3261 ist der Max-Forwards header vorgeschrieben, wird aber oft trotzdem nicht gesendet. Wenn aktiviert, lehnt Twinkle SIP-Anfragen ohne Max-Forwards header ab. Put &registration expiry time in contact header Anmeldedaue&r im Contact-Header übertragen Alt+R Alt+R In a REGISTER message the expiry time for registration can be put in the Contact header or in the Expires header. If you tick this box it will be put in the Contact header, otherwise it goes in the Expires header. In einer REGISTER-Anfrage kann die Ablaufzeit (expiry, "haltbar:") der Anmeldung sowohl im Contact-Header als auch im Expires-Header übertragen werden. Wenn aktiviert, sendet Twinkle im Contact-header, sonst im Expires-header. &Use compact header names &kompakte Headernamen Indicates if compact header names should be used for headers that have a compact form. Wenn aktiviert, für Headernamen die kurze Form verwenden, soweit eine existiert. Allow SDP change during call setup Erlaube SDP-Änderungen beim Rufaufbau <p>A SIP UAS may send SDP in a 1XX response for early media, e.g. ringing tone. When the call is answered the SIP UAS should send the same SDP in the 200 OK response according to RFC 3261. Once SDP has been received, SDP in subsequent responses should be discarded.</p> <p>By allowing SDP to change during call setup, Twinkle will not discard SDP in subsequent responses and modify the media stream if the SDP is changed. When the SDP in a response is changed, it must have a new version number in the o= line.</p> <p>Ein SIP UAS kann ein SDP in einer 1XX Antwort für early media, z.B. bei "Freizeichen", senden. Wenn das Gespräch aufgebaut wird, sollte der SIP UAS das selbe SDP in der "200 OK"-Antwort senden. Nach Empfang eines SDP sollten alle folgenden verworfen werden. Soweit die reine Lehre nach RFC 3261.</p> <p>Wenn erlaubt wird, dass sich SDP wahrend des Gesprächsaufbaus ändert, verwirft Twinkle SDPs in Folgeantworten nicht, sondern ändert die Eigenschaften des RTP-Mediastreams (z.B. codec) entsprechend. Ein geändertes SDP muss eine neue Versionsnummer in der "o="-Zeile haben.</p> <p> Twinkle creates a unique contact header value by combining the SIP user name and domain: </p> <p> <tt>&nbsp;user_domain@local_ip</tt> </p> <p> This way 2 user profiles, having the same user name but different domain names, have unique contact addresses and hence can be activated simultaneously. </p> <p> Some proxies do not handle a contact header value like this. You can disable this option to get a contact header value like this: </p> <p> <tt>&nbsp;user@local_ip</tt> </p> <p> This format is what most SIP phones use. </p> <p>Wenn aktiviert, erzeugt Twinkle einen eindeutigen contact header Wert durch Kombination des SIP-Nutzernamens und der Domain: <br> <tt>&nbsp;user_domain@local_ip</tt> </p> <p> So haben 2 Benutzerprofile mit selbem SIP-Nutzernamen aber unterschiedlicher Domain eindeutige contact Adressen und können so gleichzeitig aktiviert werden. </p> <p> Viele Proxies können mit solchen contact header Werten nicht umgehen. Wenn diese Option deaktiviert ist, sendet Twinkle contact header in folgendem Format: <br> <tt>&nbsp;user@local_ip</tt> </p> <p> Dieses Format wird von fast allen SIP-Telefonen verwendet. </p> <p> <b>Nutzen Sie diese Option nur, wenn Sie sie wirklich brauchen! Also wenn Sie mehrere Profile mit gleichem SIP-Benutzernamen haben.</b></p> &Encode Via, Route, Record-Route as list Via, Route, Record-Route als List&e senden The Via, Route and Record-Route headers can be encoded as a list of comma separated values or as multiple occurrences of the same header. Die Via-, Route- und Record-Route-Header können als Liste von durch Komma getrennten Werten oder als einzelne Werte übertragen werden. SIP extensions SIP Erweiterungen &100 rel (PRACK): disabled deaktiviert supported erlaubt required erforderlich preferred bevorzugt Indicates if the 100rel extension (PRACK) is supported:<br><br> <b>disabled</b>: 100rel extension is disabled <br><br> <b>supported</b>: 100rel is supported (it is added in the supported header of an outgoing INVITE). A far-end can now require a PRACK on a 1xx response. <br><br> <b>required</b>: 100rel is required (it is put in the require header of an outgoing INVITE). If an incoming INVITE indicates that it supports 100rel, then Twinkle will require a PRACK when sending a 1xx response. A call will fail when the far-end does not support 100rel. <br><br> <b>preferred</b>: Similar to required, but if a call fails because the far-end indicates it does not support 100rel (420 response) then the call will be re-attempted without the 100rel requirement. Definiert die Art der Unterstützung für 100rel extension (PRACK):<br><br> <b>deaktiviert</b>: 100rel extension wird nicht unterstützt <br><br> <b>erlaubt</b>: 100rel wird unterstützt (wird im "supported header" abgehender INVITEs übertragen). Eine Gegenstelle kann dann ein PRACK auf eine 1xx Antwort anfordern. <br><br> <b>erforderlich</b>: 100rel wird angefordert (wird im "require header" abgehender INVITEs übertragen). Wenn die Gegenstelle ein INVITE sendet (=anruft) und darin signalisiert, dass sie 100rel unterstützt, dann fordert Twinkle beim senden einer 1xx-Antwort PRACK an. Unterstützt die Gwegenstelle 100rel nicht, kommt die Verbindung nicht zustande. <br><br> <b>bevorzugt</b>: Wie "erforderlich", ausser dass auch dann ein Gespräch zustande kommt, wenn die Gegenstelle 100rel nicht unterstützt. Diese Einstellung beeinflusst das Verhalten bei "early media" (z.B. "Freizeichen"). REFER Call transfer (REFER) Rufweitervermittlung (REFER) Allow call &transfer (incoming REFER) GgSt darf vermi&tteln (eingehender REFER) Alt+T Alt+T Indicates if Twinkle should transfer a call if a REFER request is received. Wenn aktiviert, befolgt Twinkle die Anfrage der Gegenstelle (REFER), Sie zu einer anderen Gegenstelle weiterzuvermitteln. Dies kann für Sie Kosten verursachen. As&k user permission to transfer Benutzer vor &Vermittlung fragen Alt+K Alt+V Indicates if Twinkle should ask the user before transferring a call when a REFER request is received. Wenn aktiviert, fragt Twinkle bei eingehender Vermittlungsanfrage (REFER) vor Abbau der bisherigen und Anwählen der neuen Verbindung. Im Gegensatz zum Fest- und GSM-Netz trägt bei SIP nicht der Vermittler, sondern der "Anrufende" (also der, der an die neue Gegenstelle weitervermittelt wird) die eventuellen Kosten für das neue vermittelte Gespräch. Hold call &with referrer while setting up call to transfer target T&winkle hält Gespräch als Vermittelter Alt+W Alt+W Indicates if Twinkle should put the current call on hold when a REFER request to transfer a call is received. Wenn aktiviert, übernimmt bei eingehender Vermittlungsaufforderung Twinkle es, den bisherigen Anruf zu halten. Normalerweise sollte die vermittelnde Gegenstelle dies tun. Siehe folgende Option. Standard: deaktiviert. Ho&ld call with referee before sending REFER Twink&le hält Gespräch als Vermittler Alt+L Alt+L Indicates if Twinkle should put the current call on hold when you transfer a call. Wenn aktiviert, schaltet Twinkle als Vermittler das bisherige Gespräch in den Gehalten-Zustand, bevor es der Gegenstelle ein REFER schickt. So muss die Gegenstelle dies nicht tun - siehe vorherige Option. Standard: aktiviert. Auto re&fresh subscription to refer event while call transfer is not finished Subscription &für REFER automatisch erneuern, bis Vermittlung beendet Alt+F Alt+F While a call is being transferred, the referee sends NOTIFY messages to the referrer about the progress of the transfer. These messages are only sent for a short interval which length is determined by the referee. If you tick this box, the referrer will automatically send a SUBSCRIBE to lengthen this interval if it is about to expire and the transfer has not yet been completed. Während eines Vermittlungvorgangs sendet der Vermittelte NOTIFY-Mitteilungen über den Fortgang des Gesprächsaufbaus an den Vermittler, allerdings nur für eine kurze Zeitspanne, die der Vermittelte festlegt. Wenn aktiviert, sendet der Vermittler (Twinkle) automatisch SUBCRIBEs, um diese Zeit zu verlängern bis der Vermittlungsvorgang abgeschlossen ist. NAT traversal NAT Durchtunnelung &NAT traversal not needed &NAT Durchtunnelung unnötig Alt+N Alt+N Choose this option when there is no NAT device between you and your SIP proxy or when your SIP provider offers hosted NAT traversal. Wählen Sie diese Option, wenn sich zwischen Twinkle und Ihrem SIP-Proxy keine NAT (Router) befindet, wenn zwar eine NAT existiert, aber ein Application Level Gateway (ALG) im Router den SIP-Betrieb unterstützt, oder wenn Ihr SIP-Provider "hosted NAT traversal" unterstützt (ein Weg, wie der Provider Probleme mit NAT umgehen kann). Im Zweifelsfall sollten Sie zuerst versuchen, ob diese Einstellung bei Ihnen funktioniert, auch wenn Sie einen Router / NAT haben. &Use statically configured public IP address inside SIP messages Fest voreingestellte &Adresse in SIP-Telegrammen verwenden Indicates if Twinkle should use the public IP address specified in the next field inside SIP message, i.e. in SIP headers and SDP body instead of the IP address of your network interface.<br><br> When you choose this option you have to create static address mappings in your NAT device as well. You have to map the RTP ports on the public IP address to the same ports on the private IP address of your PC. Wenn aktiviert, verwendet Twinkle in SIP-Telegrammen, also Headern und Body, die im nächsten Feld angegebene öffentliche Adresse anstatt der automatisch ermittelten Adresse Ihres Netzwerkanschlusses.<br><br> Wenn Sie diese Option verwenden, müssen Sie auch in Ihrer NAT die entsprechenden RTP-Ports auf Ihren Rechner durchleiten. Use &STUN &STUN aktivieren Choose this option when your SIP provider offers a STUN server for NAT traversal. Aktivieren Sie diese Option, wenn Ihr SIP-Provider einen STUN-Server zum Durchtunneln der NAT anbietet. S&TUN server: S&TUN-Server: The hostname, domain name or IP address of the STUN server. Der Domainname, IP-Adresse oder Hostname des STUN-Servers (gegebenenfalls incl ":<portnr>", also z.B. "stunsrv.de:10000"). &Public IP address: Öffentl. &Adresse: The public IP address of your NAT. Die öffentliche Adresse (IP, DynDNS-domain), unter der Ihre NAT(/Router) im Internet erreichbar ist. Diese Option ist nur bei unveränderlicher Adresse sinnvoll. Telephone numbers Telefonnummern Only &display user part of URI for telephone number Bei Telefonnumern nur User-Teil &der URI anzeigen If a URI indicates a telephone number, then only display the user part. E.g. if a call comes in from sip:123456@twinklephone.com then display only "123456" to the user. A URI indicates a telephone number if it contains the "user=phone" parameter or when it has a numerical user part and you ticked the next option. Wenn eine URI eine Telefonnummer darstellt, dann nur den User-Teil anzeigen. Kommt z.B. ein Anruf von sip:12345@einprovider.com, dann zeigt Twinkle nur "12345" als Adresse. Twinkle betrachtet eine URI als "Telefonnummer", wenn sie entweder den Zusatz "user=phone" enthält, oder wenn die nächste Option aktiv ist und Twinkle den User-Teil als Nummer einschätzt. &URI with numerical user part is a telephone number &URI mit numerischem User-Teil ist Telefonnummer If you tick this option, then Twinkle considers a SIP address that has a user part that consists of digits, *, #, + and special symbols only as a telephone number. In an outgoing message, Twinkle will add the "user=phone" parameter to such a URI. Wenn aktiviert, betrachtet Twinkle jede SIP-Adresse als "Telefonnummer", die nur Ziffern, *, #, + und Sonderzeichen (s.o.) im User-Teil hat. In abgehenden SIP-Mitteilungen hängt Twinkle an solche Adressen den Parameter "user=phone" an. Achtung: z.B. sipgate verändert(e) subtil sein Verhalten bei manchen Funktionen, sobald dieser Parameter mitgesendet wird. &Remove special symbols from numerical dial strings Sonde&rzeichen aus Wählstring entfernen Telephone numbers are often written with special symbols like dashes and brackets to make them readable to humans. When you dial such a number the special symbols must not be dialed. To allow you to simply copy/paste such a number into Twinkle, Twinkle can remove these symbols when you hit the dial button. Telefonnumern werden oft unter Verwendung von Sonderzeichen wie "(", ")", " "(Leerzeichen), "-" usw. angegeben, um sie für Menschen leichter lesbar zu gestalten. Beim Wählen, insbesondere einer SIP-Adresse, dürfen diese Zeichen nicht mit angegeben werden. Um das Wählen durch Kopieren und Einfügen, direktes Anklicken im Adressbuch usw. zu vereinfachen, kann man Twinkle eine Liste mit unzulässigen Zeichen angeben, die vor dem eigentlichen Wählen automatisch zu löschen sind. &Special symbols: unzul. &Sonderzeichen: The special symbols that may be part of a telephone number for nice formatting, but must be removed when dialing. Liste aller Sonderzeichen, die Twinkle aus den zu wählenden Nummern entfernen soll. Number conversion Nummernkonvertierung Match expression Suchausdruck Replace Ersetzung <p> Often the format of the telphone numbers you need to dial is different from the format of the telephone numbers stored in your address book, e.g. your numbers start with a +-symbol followed by a country code, but your provider expects '00' instead of the '+', or you are at the office and all your numbers need to be prefixed with a '9' to access an outside line. Here you can specify number format conversion using Perl style regular expressions and format strings. </p> <p> For each number you dial, Twinkle will try to find a match in the list of match expressions. For the first match it finds, the number will be replaced with the format string. If no match is found, the number stays unchanged. </p> <p> The number conversion rules are also applied to incoming calls, so the numbers are displayed in the format you want. </p> <h3>Example 1</h3> <p> Assume your country code is 31 and you have stored all numbers in your address book in full international number format, e.g. +318712345678. For dialling numbers in your own country you want to strip of the '+31' and replace it by a '0'. For dialling numbers abroad you just want to replace the '+' by '00'. </p> <p> The following rules will do the trick: </p> <blockquote> <tt> Match expression = \+31([0-9]*) , Replace = 0$1<br> Match expression = \+([0-9]*) , Replace = 00$1</br> </tt> </blockquote> <h3>Example 2</h3> <p> You are at work and all telephone numbers starting with a 0 should be prefixed with a 9 for an outside line. </p> <blockquote> <tt> Match expression = 0[0-9]* , Replace = 9$&<br> </tt> </blockquote> <p> Oftmals ist das Format der Telefonnummern, das z.B. der Provider erwartet, nicht identisch mit dem Format der im Adressbuch gespeicherten Nummern. Beispielsweise könnten Ihre Nummern mit "+" und dem Ländercode beginnen, Ihr Provider erwartet aber "00" statt des "+". Oder Sie sind an die lokale SIP-Installation in Ihrer Firma angeschlossen und müssen eine Amtsholziffer vorwählen. Hier können Sie unter Verwendung von Such- und Ersetzungs-Mustern (nach Art regulärer Ausdrücke a la Perl) allgemeingültige Regeln zur Umwandlung von Telefonnummern einrichten. </p> <p> Bei jeden Wahlversuch versucht Twinkle, für die zu wählende Nummer (den User-Teil der vollen SIP-Adresse) einen passenden Ausdruck in der Liste der Suchmuster zu finden. Der zum ersten passenden Suchmuster gehörende Ersetzungsausdruck ersetzt die Original-Nummer, wobei durch "(" ")" umschlossene Platzhalter im Suchausdruck (z.B. "([0-9]*)" für "beliebig viele Ziffern") die durch sie "geschluckten" Zeichen zur entsprechenden Variablen (z.B. "$1" für den ersten Platzhalter) im Ersetzungsausdruck transportieren (siehe `man 7 regex` oder konqueror:"#regex"). Wird kein passendes Suchmuster gefunden, bleibt die Nummer unverändert. </p> <p> Die Regeln werden auch auf die Absenderangaben eingehender Rufe angewendet, um diese Nummern gleich in das von Ihnen gewünschte Format zu wandeln. (!!! <i>[bug? Amtsziffer 0. d.Üs.]</i> ) </p> <h3>Beispiel 1</h3> <p> Angenommen Ihr Ländercode ist "49" für Deutschland, und Sie haben auch viele Inlandnummern in Ihrem Adressbuch in internationalem Nummernformat gespeichert, also z.B. +49 911 2345678. Ihr Provider erwartet für innerdeutsche Gespräche aber 0911 2345678. Also möchten Sie die '+49' durch '0' ersetzen. Für Auslandsgespräche möchten Sie '+' durch '00' ersetzen. </p> <p> Sie benötigen hierzu folgende Regeln, in dieser Reihenfolge: </p> <blockquote> <tt> Suchausdruck = \+49([0-9]*) , Ersetzung =0$1<br> Suchausdruck = \+([0-9]*) , Ersetzung = 00$1</br> </tt> </blockquote> <h3>Beispiel 2</h3> <p> Sie befinden sich an einer Telefonanlage und alle Nummern mit 0 als erste Ziffer sollen die Amtsholziffer 9 vorangestellt bekommen. </p> <blockquote> <tt> Suchausdruck = 0[0-9]* , Ersetzung = 9$&<br> </tt> </blockquote> ( $& ist eine spezielle Variable, die die gesamte Originalnummer überträgt)<br> Anmerkung: Sie können diese Regel nicht einfach nur als dritte nach denen aus Beispiel 1 angeben, denn es wird immer nur die erste zutreffende Regel angewendet. Stattdessen müssten die Ersetzungen der Regeln 1 und 2 in "90$1" u. "900$1" geändert werden Move the selected number conversion rule upwards in the list. Regel in der Liste nach oben verschieben. Move the selected number conversion rule downwards in the list. Regel in der Liste nach unten verschieben. &Add &Neu Add a number conversion rule. Neue Regel erzeugen. Re&move &Löschen Remove the selected number conversion rule. Die ausgewählte Regel löschen. &Edit B&earbeiten Edit the selected number conversion rule. Die ausgewählte Regel ändern. Type a telephone number here an press the Test button to see how it is converted by the list of number conversion rules. Tippen Sie eine Nummer und klicken Sie "Test", um das Ergebnis der Umwandlung durch die Regeln zu sehen. &Test &Testen Test how a number is converted by the number conversion rules. Die Regeln mit der Nummer links testen und Resultat anzeigen. for STUN Sekunden Keep alive timer for the STUN protocol. If you have enabled STUN, then Twinkle will send keep alive packets at this interval rate to keep the address bindings in your NAT device alive. Zeitgeber für das STUN-Protokoll. Wenn STUN aktiviert ist, werden die STUN-keep-alive Datenpakete in diesem Zeitabstand von Twinkle gesendet. Damit der Router die Zuordnung zwischen interner und externer Adresse nicht aus der NAT-Adresstabelle löscht, frischen diese keep-alive-Pakete die Zuordnung in der NAT rechtzeitig auf. Dieser Wert ist daher von der eingesetzten NAT abhängig und sollte nicht zu gross gezählt werden. When an incoming call is received, this timer is started. If the user answers the call, the timer is stopped. If the timer expires before the user answers the call, then Twinkle will reject the call with a "480 User Not Responding". Wenn ein Anruf eingeht, beginnt dieser Zeitgeber abzulaufen. Wird der Ruf bis zum Ende der Zeitspanne nicht angenommen, sendet Twinkle "480 User Not Responding" und weist so den Anruf ab. NAT &keep alive: &STUN NAT-keep-alive alle: &No answer: "&Nicht erreichbar" nach: Ring &back tone: &Freizeichen: <p> Specify the file name of a .wav file that you want to be played as ring back tone for this user. </p> <p> This ring back tone overrides the ring back tone settings in the system settings. </p> <p>Geben Sie hier den Namen der .wav-Datei für das Freizeichen dieses Benutzerprofils an.</p> <p>Diese Einstellung ersetzt bei abgehendem Ruf von diesem Benutzerprofil die Auswahl für "Freizeichen" aus den Systemeinstellungen.</p> <p> Specify the file name of a .wav file that you want to be played as ring tone for this user. </p> <p> This ring tone overrides the ring tone settings in the system settings. </p> <p>Geben Sie hier den Namen der .wav-Datei für den Klingelton dieses Benutzerprofils (="Nummer") an.</p> <p>Diese Einstellung ersetzt bei Anrufen an dieses Benutzerprofil die Auswahl "Klingelton" aus den Systemeinstellungen.</p> &Ring tone: &Klingelton: <p> This script is called when you release a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing SIP BYE request are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=local_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the BYE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Dieses Script wird gestartet, wenn ein Gespräch durch Sie beendet wird. </p> <h3>Environment Variablen</h3> <p> Die Inhalte aller SIP header der abgesendeten SIP BYE Anforderung werden in Environment Variablen ans Script übergeben. </p> <p> <b>TWINKLE_TRIGGER=local_release</b>. <br> <b>SIPREQUEST_METHOD=BYE</b>. <br> <b>SIPREQUEST_URI</b> enthält die request-URI des BYE. <br> <b>TWINKLE_USER_PROFILE</b> enthält den Namen des aktuell genutzten Benutzerprofils. <p> This script is called when an incoming call fails. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing SIP failure response are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=in_call_failed</b>. <b>SIPSTATUS_CODE</b> contains the status code of the failure response. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Dieses Script wird gestartet, wenn ein eingehender Ruf nicht zustande kommt, also das Klingeln endet ohne dass "abgehoben" wurde. </p> <h3>Environment Variablen</h3> <p> Die Inhalte aller SIP header der abgesendeten SIP failure Antwort werden in Environment Variablen ans Script übergeben. </p> <p> <b>TWINKLE_TRIGGER=in_call_failed</b>. <br> <b>SIPSTATUS_CODE</b> enthält den Statuscode der abgesendeten SIP failure Antwort. <br> <b>SIPSTATUS_REASON</b>enthält "reason phrase", also die "Fehler"ursache in Klartext.<br> <b>TWINKLE_USER_PROFILE</b> enthält den Namen des aktuell genutzten Benutzerprofils. <p> This script is called when the remote party releases a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming SIP BYE request are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=remote_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the BYE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Dieses Script wird gestartet, wenn ein Gespräch durch die Gegenstelle beendet wird. </p> <h3>Environment Variablen</h3> <p> Die Inhalte aller SIP header der eingehenden SIP BYE Anforderung werden in Environment Variablen ans Script übergeben. </p> <p> <b>TWINKLE_TRIGGER=remote_release</b>. <br> <b>SIPREQUEST_METHOD=BYE</b>. <br> <b>SIPREQUEST_URI</b> enthält die request-URI des BYE. <br> <b>TWINKLE_USER_PROFILE</b> enthält den Namen des aktuell genutzten Benutzerprofils. </p> <p> This script is called when the remote party answers your call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming 200 OK are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Dieses Script wird gestartet, wenn das Gespräch durch die Gegenstelle angenommen wird. </p> <h3>Environment Variablen</h3> <p> Die Inhalte aller SIP header der eingehenden "200 OK" Mitteilung werden in Environment Variablen ans Script übergeben. </p> <p> <b>TWINKLE_TRIGGER=out_call_answered</b>. <br> <b>SIPSTATUS_CODE=200</b>. <br> <b>SIPSTATUS_REASON</b> enthält "reason phrase"<br> <b>TWINKLE_USER_PROFILE</b> enthält den Namen des aktuell genutzten Benutzerprofils. <p> This script is called when you answer an incoming call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing 200 OK are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=in_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Dieses Script wird gestartet, wenn Sie einen Anruf entgegennehmen. </p> <h3>Environment Variablen</h3> <p> Die Inhalte aller SIP header der gesendeten "200 OK" Antwort werden in Environment Variablen ans Script übergeben. </p> <p> <b>TWINKLE_TRIGGER=in_call_answered</b>. <br> <b>SIPSTATUS_CODE=200</b>. <br> <b>SIPSTATUS_REASON</b> enthält "reason phrase"<br> <b>TWINKLE_USER_PROFILE</b> enthält den Namen des aktuell genutzten Benutzerprofils. Call released locall&y: Gespräch &lokal beendet: <p> This script is called when an outgoing call fails. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming SIP failure response are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call_failed</b>. <b>SIPSTATUS_CODE</b> contains the status code of the failure response. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Dieses Script wird gestartet, wenn ein abgehender Anruf nicht zustande kommt, z.B. wegen timeout, DND usw. </p> <h3>Environment Variablen</h3> <p> Die Inhalte aller SIP header der empfangenen SIP failure Antwort werden in Environment Variablen ans Script übergeben. </p> <p> <b>TWINKLE_TRIGGER=out_call_failed</b>. <br> <b>SIPSTATUS_CODE</b> enthält den Statuscode der abgesendeten SIP failure Antwort.<br> <b>SIPSTATUS_REASON</b> enthält "reason phrase", also die "Fehler"ursache in Klartext.<br> <b>TWINKLE_USER_PROFILE</b> enthält den Namen des aktuell genutzten Benutzerprofils. <p> This script is called when you make a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing INVITE are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call</b>. <b>SIPREQUEST_METHOD=INVITE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the INVITE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Dieses Script wird gestartet, wenn Sie einen Anruf tätigen. </p> <h3>Environment Variablen</h3> <p> Die Inhalte aller SIP header der abgesendeten SIP INVITE Anforderung werden in Environment Variablen ans Script übergeben. </p> <p> <b>TWINKLE_TRIGGER=out_call</b>.<br> <b>SIPREQUEST_METHOD=INVITE</b>.<br> <b>SIPREQUEST_URI</b> enthält die request-URI des INVITE.<br> <b>TWINKLE_USER_PROFILE</b> enthält den Namen des aktuell genutzten Benutzerprofils. Outgoing call a&nswered: Abgehe&nder Ruf angenommen: Incoming call &failed: Eingehender Ruf er&folglos: &Incoming call: E&ingehender Ruf: Call released &remotely: Gesp&rächsende durch Gegenstelle: Incoming call &answered: Eingehender Ruf &angenommen: O&utgoing call: Abgehender R&uf: Out&going call failed: Ab&gehender Ruf erfolglos: &Enable ZRTP/SRTP encryption ZRTP/SRTP V&erschlüsselung When ZRTP/SRTP is enabled, then Twinkle will try to encrypt the audio of each call you originate or receive. Encryption will only succeed if the remote party has ZRTP/SRTP support enabled. If the remote party does not support ZRTP/SRTP, then the audio channel will stay unecrypted. Wenn aktiviert, versucht Twinkle bei allen abgehenden und ankommenden Geprächen die Sprachdaten zu verschlüsseln. Hierzu muss natürlich die Gegenstelle ebenfalls ZRTP/SRTP unterstützen, andernfalls bleibt das Gespräch unverschlüsselt. ZRTP settings ZRTP Einstellungen O&nly encrypt audio if remote party indicated ZRTP support in SDP &Nur verschlüsseln, wenn Gegenstelle ZRTP-Unterstützung im SDP meldet A SIP endpoint supporting ZRTP may indicate ZRTP support during call setup in its signalling. Enabling this option will cause Twinkle only to encrypt calls when the remote party indicates ZRTP support. Eine ZRTP-fähige SIP-Gegenstelle kann diese Fähigkeit schon während des Gesprächsaufbaus mitteilen. Wenn aktiviert, versucht Twinkle nur bei solchen Gegenstellen, eine Verschlüsselung auszuhandeln. &Indicate ZRTP support in SDP ZRTP-Unterstützung &im SDP mitteilen Twinkle will indicate ZRTP support during call setup in its signalling. Wenn aktiviert, meldet Twinkle der Gegenstelle beim Gesprächsaufbau im SDP, dass es ZRTP unterstützt. &Popup warning when remote party disables encryption during call &Warnen, wenn Gegenstelle auf unverschlüsselt umschaltet A remote party of an encrypted call may send a ZRTP go-clear command to stop encryption. When Twinkle receives this command it will popup a warning if this option is enabled. Die Gegenstelle kann während eines verschlüsselten Gesprächs ein ZRTP-go-clear Komando senden und damit die Verschlüsselung stoppen. Wenn aktiviert, macht Twinkle in diesem Fall mit einer Warnmeldung auf das Sicherheitsproblem aufmerksam. Dynamic payload type %1 is used more than once. Dynamische Nutzdatenkennung %1 mehrfach vergeben. You must fill in a user name for your SIP account. Sie müssen den Namensteil Ihrer SIP-Benutzerkennung angeben. You must fill in a domain name for your SIP account. This could be the hostname or IP address of your PC if you want direct PC to PC dialing. Sie müssen den domain-Teil (den Teil rechts nach @) Ihrer SIP-Benutzerkennung angeben. Häufig identisch mit der Domain Ihres SIP-Providers. Für direct-IP-to-IP, also ohne SIP-Provider, ist dies der (dyndns-)Name oder die öffentliche IP Ihres PC. Invalid user name. Unzulässiger Benutzername. Invalid domain. Unzulässige Benutzerdomain. Invalid value for registrar. Unzulässiger Wert für Registrar. Invalid value for outbound proxy. Unzulässiger Wert für outbound proxy. Value for public IP address missing. Keine öffentliche Adresse angegeben. Invalid value for STUN server. Unzulässiger Wert für STUN-Server. Ring tones Description of .wav files in file dialog Signaltöne Choose ring tone Auswahl Klingelton Ring back tones Description of .wav files in file dialog Signaltöne All files Alle Dateien Choose incoming call script Auswahl Script bei "eingehendem Ruf" Choose incoming call answered script Auswahl Script bei "eingehender Ruf angenommen" Choose incoming call failed script Auswahl Script bei "eingehender Ruf erfolglos" Choose outgoing call script Auswahl Script bei "abgehendem Ruf" Choose outgoing call answered script Auswahl Script bei "abgehender Ruf angenommen" Choose outgoing call failed script Auswahl Script bei "abgehender Ruf erfolglos" Choose local release script Auswahl Script bei "Gespräch lokal beendet" Choose remote release script Auswahl Script bei "Gespräch durch Gegenstelle beendet" Voice mail Anrufbeantworter &Follow codec preference from far end on incoming calls Gegenstelle wählt Codecs bei eingehendem Ru&f <p> For incoming calls, follow the preference from the far-end (SDP offer). Pick the first codec from the SDP offer that is also in the list of active codecs. <p> If you disable this option, then the first codec from the active codecs that is also in the SDP offer is picked. Wenn aktiviert: Bei ankomendem Anruf richtet sich Twinkle bevorzugt nach der Liste erlaubter Codecs von der Gegenstelle (SDP offer). Konkret wird der erste Codec der Ggst.-Wunschliste verwendet, der auch von Twinkle in der aktuellen Einstellung unterstützt wird. Wenn deaktiviert, verwendet Twinkle den ertsen Codec der eigenen Liste, der auch von der GgSt. untersützt wird. Follow codec &preference from far end on outgoing calls Gegenstelle wählt Codecs bei a&bgehendem Ruf <p> For outgoing calls, follow the preference from the far-end (SDP answer). Pick the first codec from the SDP answer that is also in the list of active codecs. <p> If you disable this option, then the first codec from the active codecs that is also in the SDP answer is picked. Wenn aktiviert: Bei abgehendem Ruf richtet sich Twinkle bevorzugt nach der Liste erlaubter Codecs von der Gegenstelle (SDP answer). Konkret wird der erste Codec der Ggst.-Wunschliste verwendet, der auch von Twinkle in der aktuellen Einstellung unterstützt wird. Wenn deaktiviert, verwendet Twinkle den ertsen Codec der eigenen Liste, der auch von der GgSt. untersützt wird, also in der SDP-Answer-Liste steht. Codeword &packing order: Datenanordnung (codeword &packing order): RFC 3551 ATM AAL2 There are 2 standards to pack the G.726 codewords into an RTP packet. RFC 3551 is the default packing method. Some SIP devices use ATM AAL2 however. If you experience bad quality using G.726 with RFC 3551 packing, then try ATM AAL2 packing. Es gibt 2 Methoden, die G.726 codewords in ein RTP-Paket anzuordnen. Standard ist RFC 3551. Einige SIP-Provider nutzen allerdings ATM AAL2. Wenn die Tonübertragung bei Verwendung des G.726-Codecs gestört ist, versuchen Sie hier die andere Einstellung. Replaces Replaces Indicates if the Replaces-extenstion is supported. Wenn aktiviert, unterstützt Twinkle Replaces-Extension bei PRACK. Attended refer to AoR (Address of Record) Vermittlung mit Rückfrage verwendet "Address of Record" An attended call transfer should use the contact URI as a refer target. A contact URI may not be globally routable however. Alternatively the AoR (Address of Record) may be used. A disadvantage is that the AoR may route to multiple endpoints in case of forking whereas the contact URI routes to a single endoint. Eine Vermittlung mit Rückfrage sollte die Contact-URI als Zieladresse nutzen, um der vermittelten GgSt die neu zu schaltende Verbindung mitzuteilen. Diese Adresse kann allerdings evtl. nicht global gültig d.h. "route-"bar sein. Das vermittelte Gespräch kommt dann beim neuen Ziel nicht an. Alternativ kann Twinkle die AoR (Address of Record) nutzen. Nachteil hierbei: diese ist bei mehreren unter gleichem SIP-Benutzerkonto angemeldeten Endgeräten nicht eindeutig, so dass von der vermittelten GgSt (eigentlich vom Provider) alle Endgeräte angesprochen werden und einen Anuf signalisieren. Privacy Datenschutz Privacy options Datenschutz-Einstellungen &Send P-Preferred-Identity header when hiding user identity &Sende "P-Preferred-Identity Header" bei "Absender verbergen" Include a P-Preferred-Identity header with your identity in an INVITE request for a call with identity hiding. Wenn aktiviert, wird zusammen mit der Absenderangabe ein "P-Preferred-Identity Header" beim INVITE gesendet, falls "Absender verbergen" aktiv ist. <p> You can customize the way Twinkle handles incoming calls. Twinkle can call a script when a call comes in. Based on the output of the script Twinkle accepts, rejects or redirects the call. When accepting the call, the ring tone can be customized by the script as well. The script can be any executable program. </p> <p> <b>Note:</b> Twinkle pauses while your script runs. It is recommended that your script does not take more than 200 ms. When you need more time, you can send the parameters followed by <b>end</b> and keep on running. Twinkle will continue when it receives the <b>end</b> parameter. </p> <p> With your script you can customize call handling by outputting one or more of the following parameters to stdout. Each parameter should be on a separate line. </p> <p> <blockquote> <tt> action=[ continue | reject | dnd | redirect | autoanswer ]<br> reason=&lt;string&gt;<br> contact=&lt;address to redirect to&gt;<br> caller_name=&lt;name of caller to display&gt;<br> ringtone=&lt;file name of .wav file&gt;<br> display_msg=&lt;message to show on display&gt;<br> end<br> </tt> </blockquote> </p> <h2>Parameters</h2> <h3>action</h3> <p> <b>continue</b> - continue call handling as usual<br> <b>reject</b> - reject call<br> <b>dnd</b> - deny call with do not disturb indication<br> <b>redirect</b> - redirect call to address specified by <b>contact</b><br> <b>autoanswer</b> - automatically answer a call<br> </p> <p> When the script does not write an action to stdout, then the default action is continue. </p> <p> <b>reason: </b> With the reason parameter you can set the reason string for reject or dnd. This might be shown to the far-end user. </p> <p> <b>caller_name: </b> This parameter will override the display name of the caller. </p> <p> <b>ringtone: </b> The ringtone parameter specifies the .wav file that will be played as ring tone when action is continue. </p> <h2>Environment variables</h2> <p> The values of all SIP headers in the incoming INVITE message are passed in environment variables to your script. The variable names are formatted as <b>SIP_&lt;HEADER_NAME&gt;</b> E.g. SIP_FROM contains the value of the from header. </p> <p> TWINKLE_TRIGGER=in_call. SIPREQUEST_METHOD=INVITE. The request-URI of the INVITE will be passed in <b>SIPREQUEST_URI</b>. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Dieses Script wird gerufen, wenn ein INVITE (Anruf) ankommt. <br> Bitte lesen Sie im Handbuch unter "/usr/share/doc/packages/twinkle/..." oder "http://twinklephone.com" die ausführliche Beschreibung! </p> <h3>Rückgabewerte</h3> - print nach STDOUT (z.B. `echo "action=dnd"`), ein Wert pro Zeile: <br> <tt>action=[ continue | reject | dnd | redirect | autoanswer ]<br></tt> <blockquote> <i>continue</i> - Anrufverarbeitung normal fortsetzen (default)<br> <i>reject</i> - Ruf abweisen<br> <i>dnd</i> - Ruf ablehnen mit Hinweis "do not disturb"<br> <i>redirect</i> - Ruf umleiten nach <tt>contact</tt> (siehe dort)<br> <i>autoanswer</i> - Ruf "automatisch" annehmen<br> </blockquote> <br> <tt>reason=&lt;string&gt; </tt>für dnd und reject (Anzeige bei GgSt)<br> <tt>contact=&lt;Umleitadresse&gt; </tt>für redirect<br> <tt>caller_name=&lt;neuer Displayname des Anrufers&gt; </tt>ersetzt evtl. vorh. displayname aus INVITE<br> <tt>ringtone=&lt;Dateiname des .wav file&gt; </tt>Klingelton, speziell f. diesen Anruf (nur bei <i>continue</i> ;-)<br> <tt>display_msg=&lt;belieb. Hinweis für Detailanzeige Hauptfenster&gt;</tt><br> <tt>end </tt>Twinkle wertet alle Rückgaben aus, schliesst STDOUT des Scripts(!), und arbeitet weiter<br> </tt> </p> <p> <h3>Environment Variablen</h3> <p> Die Werte aller SIP header des eingehenden INVITE werden in Environmentvariablen ans Script übergeben. Aufbau der Variablennamen: <b>SIP_&lt;HEADER_NAME&gt;</b> - z.B. SIP_FROM enthält Wert des "from header". </p> <p> TWINKLE_TRIGGER=in_call. <br> SIPREQUEST_METHOD=INVITE. <br> SIPREQUEST_URI enthält request-URI des INVITE.<br> TWINKLE_USER_PROFILE enthält Name des Benutzerprofils, für das der Ruf einging. &Voice mail address: Anrufbeantworter Nr/Adr: The SIP address or telephone number to access your voice mail. Die SIP-Adresse bzw. Telefonnr., unter der Ihr vom Provider zur Verfügung gestellter Anrufbeantworter abrufbar ist. Oft gibt der Provider zwei Nummern an, eine zum Abruf über beliebige Telefone (zB. "0049 211 58000111") und eine zum SIP-Abruf (zB. "50000") - dann sollte hier die SIP-Nummer angegeben werden. Unsollicited Asterisk-Modus Sollicited RFC 3842 <H2>Message waiting indication type</H2> <p> If your provider offers the message waiting indication service, then Twinkle can show you when new voice mail messages are waiting. Ask your provider which type of message waiting indication is offered. </p> <H3>Unsollicited</H3> <p> Asterisk provides unsollicited message waiting indication. </p> <H3>Sollicited</H3> <p> Sollicited message waiting indication as specified by RFC 3842. </p> <H2>Message waiting indication Typ</H2> <p> Wenn Ihr SIP-Provider "message waiting indication" (MWI, Benachrichtigung über aufgezeichnete Nachrichten) anbietet, kann Twinkle Sie über neue und schon abgehörte Nachrichten auf Ihrem SIP-Anrufbeantworter informieren. Abhängig von Ihrem Provider bzw. dem von Ihnen genutzten Anrufbeantworterdienst müssen Sie hier eines der folgenden Verfahren einstellen: </p> <H3>Asterisk</H3> <p> Asterisk unterstützt im allg. "unsollicited message waiting indication". </p> <H3>RFC 3842</H3> <p> "Sollicited message waiting indication" entsprechend RFC 3842 Spezifikation (z.B. für "sipgate.de"). </p> &MWI type: &MWI Typ: Sollicited MWI RFC 3842 Subscription &duration: Anmel&dung gültig: Mailbox &user name: Mailbox Ben&utzername: The hostname, domain name or IP address of your voice mailbox server. Der Domainname, IP-Adresse oder Hostname des Voice-Mailbox-Servers. Versuchen Sie die Voreinstellung (=Domain Ihres Benutzernamens), falls Ihr Provider nichts anderes mitgeteilt hat. For sollicited MWI, an endpoint subscribes to the message status for a limited duration. Just before the duration expires, the endpoint should refresh the subscription. Bei RFC 3842 MWI meldet sich das Endgerät (Twinkle) für eine gewisse Dauer beim Server zum Empfang von Benachrichtigungen an (SUBSCRIBE), und sollte diese Anmeldung vor Ablauf erneuern. Ähnlich der "expiry time" / "haltbar" für REGISTER, siehe SIP-Server. Your user name for accessing your voice mailbox. Ihr Benutzername zum Zugriff auf Ihre Voice-Mailbox (Anrufbeantworter). Wenn Ihr Provider nichts anderes mitteilt, versuchen Sie die Vorgabe (=Ihr SIP-Benutzername). Mailbox &server: Mailbox-&Server: Via outbound &proxy Via Outbound-&Proxy: Check this option if Twinkle should send SIP messages to the mailbox server via the outbound proxy. Wenn aktiviert, sendet Twinkle SIP-Anfragen an die Mailbox über den Outbound-Proxy. You must fill in a mailbox user name. Sie müssen einen Mailbox-Benutzernamen angeben. You must fill in a mailbox server Sie müssen den Mailbox-Server angeben. Invalid mailbox server. Unzulässiger Name für Mailbox-Server. Invalid mailbox user name. Unzulässiger Mailbox-Benutzername. Use domain &name to create a unique contact header value Domain-&Name benutzen für eindeutigen Contact-Header Select ring back tone file. Dateiauswahl Freizeichen. Select ring tone file. Dateiauswahl Klingelton. Select script file. Dateiauswahl Scriptfile / Programm. %1 converts to %2 Vor Konvertierung: <b>%1</b><br> Nach Konvertierung: <b>%2</b> Instant message Instant Message Presence Online-Status &Maximum number of sessions: &Max. Anzahl IM-Fenster: When you have this number of instant message sessions open, new incoming message sessions will be rejected. Hier können Sie die Anzahl gleichzeitig offener IM-Fenster für dieses Benutzerprofil als Empfänger begrenzen.<br> Bei Erreichen der Obergrenze erhält jeder weitere Absender einer Instant Message den Hinweis "468 Besetzt". <br> Sie können diese Einstellung auf 0 setzen, wenn Sie keine ankommenden Instant Messages wünschen. Your presence Ihr Online-Status &Publish availability at startup Erreichbarkeit beim Start &veröffentlichen Publish your availability at startup. Wenn aktiviert, veröffentlicht Twinkle Ihren Online-Status als "online", sobald das Benutzerprofil aktivieren. Beachten Sie, dass Sie trotz allem solange nicht erreichbar sind, bis Sie sich bei, SIP-Server angemeldet haben - siehe Menü "Anmeldung", sowie hier "SIP Server" "Bei Profilstart anmelden". Buddy presence Buddy Online-Status Publication &refresh interval (sec): Erneut ve&röffentlichen nach (Sek.): Refresh rate of presence publications. Die Refreshzeit für die Veröffentlichung des Online-Status in Sekunden. Damit der "presence server" z.B. eine unterbrochene Verbindung schnell bemerkt, kann es sinnvoll sein, hier wesentlich kürzere Werte als den Standard "3600" einzutragen. &Subscription refresh interval (sec): "&Subscribe" erneut nach (Sek.): Refresh rate of presence subscriptions. Die Refreshzeit für die Anmeldung durch "SUBSCRIBE" zum Erhalten von Online-Status-Mitteilungen über die Ereichbarkeit der Buddies unter diesem Benutzerprofil. Standard "3600". Transport/NAT Übertragung/NAT Add q-value to registration Verwende q-Wert bei der Anmeldung The q-value indicates the priority of your registered device. If besides Twinkle you register other SIP devices for this account, then the network may use these values to determine which device to try first when delivering a call. Falls mehrere Geräte auf die gleiche SIP-Benutzerkennung angemeldet werden, kann der Provider den q-Wert dazu verwenden, die Reihenfolge festzulegen, in der ein eingehender Ruf an die Geräte zugetellt wird. The q-value is a value between 0.000 and 1.000. A higher value means a higher priority. Der q-Wert darf zwischen 0,000 und 1,000 liegen. Ein höherer Wert bedeutet höhere Priorität. Das Gerät mit der höchsten Priorität wird als erstes angesprochen. SIP transport SIP Übertragung UDP UDP TCP TCP Transport mode for SIP. In auto mode, the size of a message determines which transport protocol is used. Messages larger than the UDP threshold are sent via TCP. Smaller messages are sent via UDP. Übertragungsmodus (TCP oder UDP) für SIP. Bei "Automatisch" wird TCP verwendet, falls die Größe der zu übertragenden Nachricht das Limit für UDP übersteigt. T&ransport protocol: Übe&rtragungsprotokoll: UDP t&hreshold: UDP &Grenzwert: bytes Bytes Messages larger than the threshold are sent via TCP. Smaller messages are sent via UDP. Nachrichten, die größer sind als der Grenzwert, werden über TCP gesendet, kleinere über UDP. Use &STUN (does not work for incoming TCP) &STUN benutzen (wirkungslos für eingehende TCP-Verbindungen) P&ersistent TCP connection TCP-V&erbindung aufrecht erhalten Keep the TCP connection established during registration open such that the SIP proxy can reuse this connection to send incoming requests. Application ping packets are sent to test if the connection is still alive. Wenn aktiviert: Twinkle hält die TCP-Verbindung aufrecht, die bei der Registrierung verwendet wurde. So kann der SIP-Proxy diese Verbindung weiterhin benutzen, um ankommende Anfragen an Twinkle weiterzuleiten. Durch Senden von "Application ping Paketen" wird ständig überprüft, ob die Verbindung weiterhin besteht. &Send composing indications when typing a message. &Sende "compositing indication" beim Schreiben einer Nachricht. Twinkle sends a composing indication when you type a message. This way the recipient can see that you are typing. Wenn aktiviert, sendet Twinkle eine "compositing indication" wenn Sie eine Nachricht tippen. So kann der Empfänger erkennen, dass Sie gerade dabei sind, eine Nachricht zu verfassen. AKA AM&F: A&KA OP: Authentication management field for AKAv1-MD5 authentication. Operator variant key for AKAv1-MD5 authentication. Prepr&ocessing Preprocessing (improves quality at remote end) &Automatic gain control Automatic gain control (AGC) is a feature that deals with the fact that the recording volume may vary by a large amount between different setups. The AGC provides a way to adjust a signal to a reference volume. This is useful because it removes the need for manual adjustment of the microphone gain. A secondary advantage is that by setting the microphone gain to a conservative (low) level, it is easier to avoid clipping. Automatic gain control &level: Automatic gain control level represents percentual value of automatic gain setting of a microphone. Recommended value is about 25%. &Voice activity detection When enabled, voice activity detection detects whether the input signal represents a speech or a silence/background noise. &Noise reduction The noise reduction can be used to reduce the amount of background noise present in the input signal. This provides higher quality speech. Acoustic &Echo Cancellation In any VoIP communication, if a speech from the remote end is played in the local loudspeaker, then it propagates in the room and is captured by the microphone. If the audio captured from the microphone is sent directly to the remote end, then the remote user hears an echo of his voice. An acoustic echo cancellation is designed to remove the acoustic echo before it is sent to the remote end. It is important to understand that the echo canceller is meant to improve the quality on the remote end. Variable &bit-rate Discontinuous &Transmission &Quality: Speex is a lossy codec, which means that it achives compression at the expense of fidelity of the input speech signal. Unlike some other speech codecs, it is possible to control the tradeoff made between quality and bit-rate. The Speex encoding process is controlled most of the time by a quality parameter that ranges from 0 to 10. bytes Use tel-URI for telephone &number Expand a dialed telephone number to a tel-URI instead of a sip-URI. Accept call &transfer request (incoming REFER) Allow call transfer while consultation in progress When you perform an attended call transfer, you normally transfer the call after you established a consultation call. If you enable this option you can transfer the call while the consultation call is still in progress. This is a non-standard implementation and may not work with all SIP devices. Enable NAT &keep alive Send UDP NAT keep alive packets. If you have enabled STUN or NAT keep alive, then Twinkle will send keep alive packets at this interval rate to keep the address bindings in your NAT device alive. WizardForm Twinkle - Wizard The hostname, domain name or IP address of the STUN server. Der Domainname, IP-Adresse oder Hostname des STUN-Servers. Twinkle versucht, unter der hier genannten Domain die korrekten Daten beim DNS-Server zu erfragen (RFC 2782). Daher genügt bei Providern, die dies unterstützen, die Domain des Anmeldeservers als Angabe. S&TUN server: S&TUN-Server: The SIP user name given to you by your provider. It is the user part in your SIP address, <b>username</b>@domain.com This could be a telephone number. <br><br> This field is mandatory. Der Nutzername, den Sie von Ihrem Provider zugewiesen bekommen haben. Dieser ist der erste Teil Ihrer vollständigen SIP-Adresse <b>nutzername</b>@domain.com . Bei vielen Providern wird dieser -eigentlich falsch- als Telefonnummer bezeichnet. <br><br> *DATEN FÜR DIESES FELD SIND ZWINGEND NOTWENDIG. &Domain*: Choose your SIP service provider. If your SIP service provider is not in the list, then select <b>Other</b> and fill in the settings you received from your provider.<br><br> If you select one of the predefined SIP service providers then you only have to fill in your name, user name, authentication name and password. Wählen Sie Ihren SIP-Provider aus, und tragen Sie dann Ihren SIP-Benutzernamen, gegebenenfalls Absendernamen, Anmeldenamen und Passwort ein.<br> Wenn Ihr SIP-Provider nicht in der Liste erscheint, wählen Sie <b>Anderer</b> und tragen Sie die Angaben entsprechend der von Ihrem Provider erhaltenen Daten ein. <p> Praktisch überall in Twinkle bekommen Sie mit <b>Umschalt-F1</b> oder <b>rechtem Mausklick</b> Hilfetexte wie diesen zu den einzelnen Feldern und Knöpfen. </p> &Authentication name: &Anmeldename: &Your name: Ihr &Absendername: Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Ihr SIP-Anmeldename. Häufig identisch mit Ihrem SIP-Nutzernamen, dann leerlassen. Falls nicht, wird Ihr Provider dies mitteilen. The domain part of your SIP address, username@<b>domain.com</b>. Instead of a real domain this could also be the hostname or IP address of your <b>SIP proxy</b>. If you want direct IP phone to IP phone communications then you fill in the hostname or IP address of your computer. <br><br> This field is mandatory. Die Domain oder IP-Adresse, unter der Sie von Ihrem Provider geführt werden bzw. im Internet erreichbar sind. Dies ist der zweite Teil ihrer vollständigen SIP-Adresse nutzername@<b>domain.com</b>. Bei vielen Providern identisch mit der Domain des Providers. Für direct-IP-to-IP (siehe Handbuch) ist hier die Adresse (DynDNS oder IP) einzutragen, unter der <b>Ihr Rechner</b> zu erreichen ist. <br><br> *DATEN FÜR DIESES FELD SIND ZWINGEND NOTWENDIG. This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. Ihr Absendername oder Pseudonym. Dieses Feld wird nur als Teil der Absenderangaben zur angerufenen/rufenden Gegernstelle übertragen und dort evtl angezeigt. Beliebige Angabe, nicht zwingend erforderlich. SIP pro&xy: SIP-Pro&xy: The hostname, domain name or IP address of your SIP proxy. If this is the same value as your domain, you may leave this field empty. Die Domain, IP-Adresse oder Hostname Ihres SIP-Proxy. Wenn dieser mit Ihrer Benutzerdomain identisch ist, lassen Sie dieses Feld leer. &SIP service provider: &SIP Service Provider (Umschalt-F1 für Hilfe): &Password: &Passwort: &User name*: N&utzername *: Your password for authentication. Ihr Anmeldepasswort. Wenn Sie dieses Feld leerlassen, müssen Sie das Passwort bei jeder Anmeldung in den dann erscheinenden Requester eintragen. &OK Alt+O Alt+O &Cancel Abbruch (Es&c) Alt+C Alt+C None (direct IP to IP calls) Keiner (direkt IP zu IP) Other Anderer User profile wizard: Benutzerprofil Wizard: You must fill in a user name for your SIP account. Sie müssen den Namensteil Ihrer SIP-Benutzerkennung angeben. You must fill in a domain name for your SIP account. This could be the hostname or IP address of your PC if you want direct PC to PC dialing. Sie müssen den domain-Teil (den Teil rechts nach @) Ihrer SIP-Benutzerkennung angeben. Häufig identisch mit der Domain Ihres SIP-Providers. Für direct-IP-to-IP, also ohne SIP-Provider, ist dies der (dyndns-)Name oder die öffentliche IP Ihres PC. Invalid value for SIP proxy. Unzulässiger Wert für SIP-Proxy. Invalid value for STUN server. Unzulässiger Wert für STUN-Server. YesNoDialog &Yes &Ja &No &Nein incoming_call Answer Reject twinkle-1.10.1/src/gui/lang/twinkle_fr.ts000066400000000000000000010254511277565361200203050ustar00rootroot00000000000000 AddressCardForm Twinkle - Address Card Twinkle - Fiche de contact &Remark: &Remarque: Infix name of contact. Titre du contact. First name of contact. Prénom du contact. &First name: &Prénom: You may place any remark about the contact here. Vous pouvez saisir une remarque sur le contact ici. &Phone: &Téléphone: &Infix name: T&itre: Phone number or SIP address of contact. Numéro de téléphone ou une adresse SIP du contact. Last name of contact. Nom du contact. &Last name: &Nom: &OK Alt+O &Cancel Annuler (Es&c) Alt+C You must fill in a name. Vous devez saisir un nom. You must fill in a phone number or SIP address. Vous devez saisir un numéro de téléphone ou une adresse SIP. AddressTableModel Name Nom Phone Téléphone Remark Remarque AuthenticationForm Twinkle - Authentication Twinkle - Authentification user No need to translate The user for which authentication is requested. L'utilisateur dont l'identification est requise. profile No need to translate The user profile of the user for which authentication is requested. Le profil d'utilisateur dont l'identification est requise. User profile: Profil d'utilisateur: User: Utilisateur: &Password: Mot de &passe: Your password for authentication. Votre mot de passe pour authentification. Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Votre nom d'authentification SIP. Souvent il est le même que votre nom d'utilisateur SIP. Cependant, il peut être différent. &User name: &Utilisateur: &OK &OK &Cancel Annuler (Es&c) Login required for realm: Login nécessaire pour Realm: realm No need to translate The realm for which you need to authenticate. Le Realm pour lequel vous devez vous identifier. BuddyForm Twinkle - Buddy Twinkle - Avatar Address book Carnet d'adresses Select an address from the address book. Sélectionner une adresse du carnet d'adresses. &Phone: &Téléphone: Name of your buddy. Nom de votre avatar. &Show availability &Montrer la disponibilité Alt+S Alt+M Check this option if you want to see the availability of your buddy. This will only work if your provider offers a presence agent. Cochez cette case si vous voulez voir la disponibilité de votre avatar. Ceci fonctionnera uniquement si votre fournisseur d'accès offre un service de présence. &Name: &Nom: SIP address your buddy. Adresse SIP de votre avatar. &OK &OK Alt+O &Cancel Annuler (Es&c) Alt+C You must fill in a name. Vous devez saisir un nom. Invalid phone. Téléphone invalide. Failed to save buddy list: %1 Echec de la sauvegarde de la liste d'avatars: %1 BuddyList Availability Disponobilité unknown inconnu offline déconnecté online connecté request rejected demande rejetée not published non publié failed to publish impossible de publier request failed demande échouée Click right to add a buddy. Cliquez à droite pour ajouter un avatar. CoreAudio Failed to open sound card Echec de l'accès à la carte son Failed to create a UDP socket (RTP) on port %1 Echec de la création de la socket UDP (RTP) sur le port %1 Failed to create audio receiver thread. Echec de la création de "audio receiver thread". Failed to create audio transmitter thread. Echec de la création de "audio transmitter thread". CoreCallHistory local user utilisateur local remote user utilisateur distant failure échec unknown inconnu in entrant out sortant DeregisterForm Twinkle - Deregister Twinkle - Déconnexion deregister all devices déconnecter toutes les interfaces &OK &Cancel Annuler (Es&c) DiamondcardProfileForm This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. C'est simplement votre nom complet, ex: Pierre Dupond. Il est utilisé pour l'affichage. Quand vous ferez un appel, ceci sera montré à votre correspondant. &Your name: &Votre nom: &Cancel Annuler (Es&c) Fill in your account ID. Fill in your PIN code. A user profile with name %1 already exists. DtmfForm Twinkle - DTMF Keypad Clavier 2 3 Over decadic A. Normally not needed. Touche de fonction A. Normalement pas nécessaire. 4 5 6 Over decadic B. Normally not needed. Touche de fonction B. Normalement pas nécessaire. 7 8 9 Over decadic C. Normally not needed. Touche de fonction C. Normalement pas nécessaire. Star (*) Etoile (*) 0 Pound (#) Point (#) Over decadic D. Normally not needed. Touche de fonction D. Normalement pas nécessaire. 1 &Close &Fermer Alt+C FreeDeskSysTray Show/Hide Montrer/Cacher Quit Quitter GUI Failed to create a UDP socket (SIP) on port %1 Impossible de créer un socket UDP (SIP) sur le port %1 The following profiles are both for user %1 Les profils suivant sont pour l'utilisateur %1 You can only run multiple profiles for different users. Des utilisateurs différents doivent avoir des profils différents. Cannot find a network interface. Twinkle will use 127.0.0.1 as the local IP address. When you connect to the network you have to restart Twinkle to use the correct IP address. Impossible de trouver une interface réseau. Twinkle utilisera 127.0.0.1 comme adresse IP locale. Quand vous vous connecterez au réseeau vous devrez redémarrer Twinkle pour utiliser la bonne adresse IP. Line %1: incoming call for %2 Ligne %1: appel entrant pour %2 Call transferred by %1 Appel tranféré par %1 Line %1: far end cancelled call. Ligne %1: appel annulé par le correspondant. Line %1: far end released call. Ligne %1: Le correspondant a raccroché. Line %1: SDP answer from far end not supported. Ligne %1: réponse SDP du correspondant non supportée. Line %1: SDP answer from far end missing. Ligne %1: réponse SDP du correspondant manquante. Line %1: Unsupported content type in answer from far end. Ligne %1: Contenu entrant non supporté. Line %1: no ACK received, call will be terminated. Ligne %1: ACK non reçu, l'appel sera annulé. Line %1: no PRACK received, call will be terminated. Ligne %1: PRACK non reçu, l'appel sera annulé. Line %1: PRACK failed. Ligne %1: PRACK échoué. Line %1: failed to cancel call. Ligne %1: impossible d'annuler l'appel. Line %1: far end answered call. Ligne %1: Le correspondant a répondu. Line %1: call failed. Ligne %1: appel échoué. The call can be redirected to: L'appel peut être redirigé vers: Line %1: call released. Ligne %1: fin d'appel. Line %1: call established. Leitung %1: appel établi. Response on terminal capability request: %1 %2 Réponse à la demande des capacités du correspondant: %1 %2 Terminal capabilities of %1 Capacités du correspondant %1 Accepted body types: Types de corps acceptés: unknown inconnu Accepted encodings: Encodage accepté: Accepted languages: Langages accepté: Allowed requests: Demandes authorisées: Supported extensions: Extensions supportées: none aucun End point type: Type de correspondant: Line %1: call retrieve failed. Ligne %1: récupération de l'appel échoué. %1, registration failed: %2 %3 %1, connexion échouée: %2 %3 %1, registration succeeded (expires = %2 seconds) %1, connexion réussie (expire %2 sec.) %1, registration failed: STUN failure %1, enregistrement échoué: echec STUN %1, de-registration succeeded: %2 %3 %1, déconnexion réussie: %2 %3 %1, fetching registrations failed: %2 %3 %1, recherche des connexions échouée: %2 %3 : you are not registered : vous n'êtes pas connecté : you have the following registrations : voici la liste des connexions : fetching registrations... : recherche des connexions... Line %1: redirecting request to Ligne %1: rediriger la demande à Redirecting request to: %1 Redirection de la demande à : %1 Line %1: DTMF detected: Ligne %1: DTMF détecté: invalid DTMF telephone event (%1) Evénnement DTMF invalid (%1) Line %1: send DTMF %2 Ligne %1: envoi DTMF %2 Line %1: far end does not support DTMF telephone events. Ligne %1: le correspondant ne supporte pas les événnements DTMF. Line %1: received notification. Ligne %1: notification reçue. Event: %1 Evénnement: %1 State: %1 Statut: %1 Reason: %1 Raison: %1 Progress: %1 %2 Progression: %1 %2 Line %1: call transfer failed. Ligne %1: transfert d'appel échoué. Line %1: call successfully transferred. Ligne %1: transfert d'appel réussi. Line %1: call transfer still in progress. Ligne %1: transfert d'appel en cours. No further notifications will be received. Aucune nouvelle notification ne sera reçue. Line %1: transferring call to %2 Ligne %1: transfert d'appel vers %2 Transfer requested by %1 Demande de transfert de %1 Line %1: Call transfer failed. Retrieving original call. Ligne %1: Transfert d'appel échoué. Recherche de l'appel original. Redirecting call Redirection d'appel User profile: Profil utilisateur: User: Utilisateur: Do you allow the call to be redirected to the following destination? Authorisez vous la redirection de l'appel vers la destination suivante? If you don't want to be asked this anymore, then you must change the settings in the SIP protocol section of the user profile. Si vous ne voulez plus qu'on vous pose cette question, vous devez modifier les paramètres dans la section "protocole SIP" du profil d'utilisateur. Redirecting request Demande de redirection Do you allow the %1 request to be redirected to the following destination? Authorisez-vous la redirection de la demande %1 vers la destination suivante? Transferring call Transfert d'appel Request to transfer call received from: Demande de transfert d'appel reçue depuis: Do you allow the call to be transferred to the following destination? Authorisez-vous le transfert de l'appel vers la destination suivante? Info: Info: Warning: Attention: Critical: Critique: Firewall / NAT discovery... Firewall / NAT Analyse... Abort Annuler Line %1 Ligne %1 Click the padlock to confirm a correct SAS. Cliquez sur "Verr Num" pour confirmer un SAS correcte. The remote user on line %1 disabled the encryption. L'utilisateur distant sur la ligne %1 a invalidé la cryptographie. Line %1: SAS confirmed. Ligne %1: SAS confirmé. Line %1: SAS confirmation reset. Ligne %1: SAS confiirmation de la remise à zéro. Line %1: call rejected. Ligne %1: appel rejeté. Line %1: call redirected. Ligne %1: appel redirigé. Failed to start conference. Lancement de la conférence échoué. Override lock file and start anyway? Outre-passer le fichier de vérouillage et démarrer quand-même? %1, STUN request failed: %2 %3 %1, STUN demande rejetée: %2 %3 %1, STUN request failed. %1, STUN demande échouée. %1, voice mail status failure. %1, echec du statut du message vocal. %1, voice mail status rejected. %1, rejet du statut du message vocal. %1, voice mailbox does not exist. %1, la boîte vocale n'existe pas. %1, voice mail status terminated. %1, statut du message vocal terminé. %1, de-registration failed: %2 %3 %1, déconnexion échouée: %2 %3 Request to transfer call received. Demande de transfert d'appel reçue. If these are users for different domains, then enable the following option in your user profile (SIP protocol) S'il y a des utilisateurs de différents domaines, activez cette option dans votre profil d'utilisateur (protcole SIP) Use domain name to create a unique contact header Utilisez le nom de domaine pour créer une entête de contact unique Failed to create a %1 socket (SIP) on port %2 Impossible de créer un socket %1 (SIP) sur le port %2 Accepted by network Accepté par le réseau Failed to save message attachment: %1 Echec de l'enregistrement de la pièce jointe: %1 Transferred by: %1 Cannot open web browser: %1 Configure your web browser in the system settings. GetAddressForm Twinkle - Select address Twinkle - Selection d'adresse Name Nom Type Type Phone Téléphone &Show only SIP addresses Montrer uniquement les adresses &SIP Alt+S Check this option when you only want to see contacts with SIP addresses, i.e. starting with "<b>sip:</b>". Cochez cette case quand vous voulez uniquement voir les contacts avec une adresse SIP, i.e. commençant par: "<b>sip:</b>". &Reload &Recharger Alt+R Reload the list of addresses from KAddressbook. Recharge la liste d'adresse de KAddressbook. &OK Alt+O &Cancel Annuler (Es&c) Alt+C &KAddressBook This list of addresses is taken from <b>KAddressBook</b>. Contacts for which you did not provide a phone number are not shown here. To add, delete or modify address information you have to use KAddressBook. La liste d'adresse est tiré de <b>KAddressBook</b>. Les contacts pour lesquels vous n'avez pas renseignés de numéro de téléphone ne sont pas montrés ici. Pour ajouter, supprimer, ou modifier une information de contact, vous devez utiliser KaddressBook. &Local address book Carnet d'adresses &local Remark Remarque Contacts in the local address book of Twinkle. Contacts dans le carnet d'adresses local de Twinkle. &Add &Ajouter Alt+A Add a new contact to the local address book. Ajouter un nouveau contact au carnet d'adresses local. &Delete &Supprimer Alt+D Alt+S Delete a contact from the local address book. Supprimer un contact du carnet d'adresses local. &Edit &Editer Alt+E Edit a contact from the local address book. Editer un contact du carnet d'adresses local. <p>You seem not to have any contacts with a phone number in <b>KAddressBook</b>, KDE's address book application. Twinkle retrieves all contacts with a phone number from KAddressBook. To manage your contacts you have to use KAddressBook.<p>As an alternative you may use Twinkle's local address book.</p> <p>Il semble qu'il n'y ait aucun contact avec un numéro de téléphone dans <b>KAddressBook</b>, le carnet d'adresses de KDE. Twinkle récupère tous les contacts avec un numéro de téléphone de KAddressBook. Pour gérer vos contacts utilisez KadressBook.</p> <p>Comme alternative, vous pouvez utiliser le carnet d'adresses local de Twinkle.</p> Are you sure you want to delete contact '%1' from the local address book? Etes-vous sûr de vouloir supprimer le contact '%1' du carnet d'adresses local? Delete contact Supprimer un contact. GetProfileNameForm Twinkle - Profile name Twinkle - Nom du profil &OK &Cancel Annuler (Es&c) Enter a name for your profile: Saisissez un nom de profil: <b>The name of your profile</b> <br><br> A profile contains your user settings, e.g. your user name and password. You have to give each profile a name. <br><br> If you have multiple SIP accounts, you can create multiple profiles. When you startup Twinkle it will show you the list of profile names from which you can select the profile you want to run. <br><br> To remember your profiles easily you could use your SIP user name as a profile name, e.g. <b>example@example.com</b> <b>Le nom de votre profil</b> <br><br> Un profil contient vos parmêtres utilisateur, ex: votre nom d'utilisateur et mot de passe. Vous devez donner un nom à chaque profil. <br><br> Si vous avez plusieurs comptes SIP, vous pouvez créer différents profils. Quand vous lancez Twinkle, il vous proposera la liste des profils pour sélectionner celuique vous voulez utiliser. <br><br> Pour vous rappeler facilement de vos profils, vous pouvez utiliser votre nom d'utilisateur SIP comme nom de profil. ex: <b>exemple@exemple.com</b> Cannot find .twinkle directory in your home directory. Impossible de trouver le dossier .twinkle dans votre dossier home. Profile already exists. Ce profil existe déjà. Rename profile '%1' to: Renomer le profil %1 en: HistoryForm Twinkle - Call History Twinkle - Historique des appels Time Heure In/Out Ent/Sort From/To de/à Subject Sujet Status Statut Call details Détails Details of the selected call record. Détails de l'appel sélectionné. View Voir &Incoming calls Appels &entrants Alt+I Alt+E Check this option to show incoming calls. Sélectionnez cette option pour voir les appels entrants. &Outgoing calls Appels &sortants Alt+O Alt+S Check this option to show outgoing calls. Sélectionnez cette option pour voir les appels sortants. &Answered calls Appels &répondus Alt+A Alt+R Check this option to show answered calls. Sélectionnez cette option pour voir les appels répondus. &Missed calls Appels en &absence Alt+M Alt+A Check this option to show missed calls. Sélectionnez cette option pour voir les appels en absence. Current &user profiles only Seulement l'&utilisateur actif Alt+U Check this option to show only calls associated with this user profile. Sélectionnez cette option pour voir seulement les appels de cet utilisateur. C&lear &Vider Alt+L Alt+V <p>Clear the complete call history.</p> <p><b>Note:</b> this will clear <b>all</b> records, also records not shown depending on the checked view options.</p> <p>Vider la totalité de l'historique.</p> <p><b>Nota:</b> ceci supprimera <b>tous</b> les enregistrements, y compris les enregistrements non affichés en fonction des options sélectionnées.</p> Alt+C Close this window. Fermer cette fenêtre. Call start: Début de l'appel: Call answer: Appel abouti: Call end: Fin de l'appel: Call duration: Durée de l'appel: Direction: Direction: From: de: To: à: Reply to: Répondre à: Referred by: Demandé par: Subject: Sujet: Released by: Terminé par: Status: Statut: Far end device: Interface de l'utilisateur distant: User profile: Profil d'utilisateur: conversation conversation Call... Appel... Delete Suppression Re: Re: Call selected address. Adresse des appels sélectionnés. Clo&se &Fermer (Esc) Alt+S Alt+F &Call Appel (&Enter) Number of calls: ### Total call duration: IncomingCallPopup %1 calling InviteForm Twinkle - Call Twinkle - Appel &To: &à: Optionally you can provide a subject here. This might be shown to the callee. Vous pouvez renseigner un sujet ici (optionnel). Il peut être montré à l'appelant. Address book Carnet d'adresses Select an address from the address book. Sélectionner une adresse du carnet d'adresses. The address that you want to call. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. L'adresse de la personne vers que vous voulez appeler. Ceci peut être une adresse SIP comme <b>sip:exemple@exemple.com</b> ou uniquement la partie utilisateur ou le numéro de téléphone d'une adresse complète. Quand vous ne spécifiez pas une adresse complète, Twinkle complètera cette adresse en utilisant le domaine défini dans votre profil utilisateur. The user that will make the call. L'utilisateur qui fera l'appel. &Subject: &Sujet: &From: &De: &OK &Cancel Annuler (Es&c) &Hide identity Cacher l'&identité Alt+H Alt+I <p> With this option you request your SIP provider to hide your identity from the called party. This will only hide your identity, e.g. your SIP address, telephone number. It does <b>not</b> hide your IP address. </p> <p> <b>Warning:</b> not all providers support identity hiding. </p> <p>Par cette option, vous demandez à votre fournisseur de services SIP de cacher votre identité vis à vis de votre correspondant. Ceci ne cachera que votre identité c'est à dire votre adresse SIP et numéro de téléphone. Ceci </b>ne<b> cache <b>pas</b> votre <b>adresse IP</p>. <p><b>Attention: </b>Tous les fournisseurs de services SIP ne supportent pas cette fonctionnalité !</p> Not all SIP providers support identity hiding. Make sure your SIP provider supports it if you really need it. Tous les fournisseurs de services SIP ne supportent pas la fonctionnalité "cacher l'identité". Assurez vous que votre fournisseur de services SIP l'authorise si vous en avez vraîment besoin. F10 LogViewForm Twinkle - Log Contents of the current log file (~/.twinkle/twinkle.log) Contenu du fichier de log (~/.twinkle/twinkle.log) &Close &Fermer Alt+C Alt+F C&lear &Supprimer Alt+L Clear the log window. This does <b>not</b> clear the log file itself. Fermer la fenêtre de log. Ceci <b>ne</b> supprime <b>pas</b> le fichier de log lui-même. MessageForm Twinkle - Instant message Twinkle - Messagerie instantanée &To: &à: The user that will send the message. L'utilisateur qui enverra le message. The address of the user that you want to send a message. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. L'adresse de la personne à qui vous voulez envoyer un message. Ceci peut être une adresse SIP comme <b>sip:exemple@exemple.com</b> ou uniquement la partie utilisateur ou le numéro de téléphone d'une adresse complète. Quand vous ne spécifiez pas une adresse complète, Twinkle complètera cette adresse en utilisant le domaine défini dans votre profil utilisateur. Address book Carnet d'adresses Select an address from the address book. Sélectionner une adresse du carnet d'adresses. &User profile: Profil &utilisateur: Conversation Conversation The exchanged messages. Les messages échangés. Type your message here and then press "send" to send it. Saisissez votre message ici, puis appuyer sure "Envoyer" pour l'envoyer. &Send &Envoyer Alt+S Alt+E Send the message. Envoyer le message. Delivery failure Echec de l'envoi Delivery notification Notification de la réception Instant message toolbar Barre d'outil de la messagerie instantanée Send file... Envoi de fichier... Send file Envoyer un fichier image size is scaled down in preview La taille de l'image est réduite en prévisualisation Open with %1... Ouvrir avec %1... Open with... Ouvrir avec... Save attachment as... Enregistrer la pièce jointe sous... File already exists. Do you want to overwrite this file? Le fichier existe déjà. Voulez-vous le remplacer ? Failed to save attachment. Impossible d'enregistrer la pièce jointe. %1 is typing a message. %1 est un message texte. F10 Size MessageFormView sending message Envoi du message MphoneForm Twinkle &Call: Label in front of combobox to enter address &Numéro: The address that you want to call. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. L'adresse de la personne vers que vous voulez appeler. Ceci peut être une adresse SIP comme <b>sip:exemple@exemple.com</b> ou uniquement la partie utilisateur ou le numéro de téléphone d'une adresse complète. Quand vous ne spécifiez pas une adresse complète, Twinkle complètera cette adresse en utilisant le domaine défini dans votre profil utilisateur. The user that will make the call. L'utilisateur qui émettera l'appel. &User: &Utilisateur: Dial Appeler Dial the address. Appeler l'appel. Address book Carnet d'adresses Select an address from the address book. Sélectionner une adresse du carnet d'adresses. Auto answer indication. Indication de réponse automatique. Message waiting indication. Indication de message en attente. Call redirect indication. Indication de redirection d'appel. Do not disturb indication. Indication "ne pas déranger". Missed call indication. Indication d'appel en absence. Registration status. Statut de la connexion. Display Affichage Line status Statut de la ligne Line &1: Ligne &1: Alt+1 Click to switch to line 1. Cliquez pour basculer sur la ligne 1. From: De: To: à: Subject: Sujet: Visual indication of line state. Indication visuelle de l'état de la ligne. idle No need to translate Call is on hold Appel en attente Voice is muted Voix coupée Conference call Conférence Transferring call Transfert d'appel <p> The padlock indicates that your voice is encrypted during transport over the network. </p> <h3>SAS - Short Authentication String</h3> <p> Both ends of an encrypted voice channel receive the same SAS on the first call. If the SAS is different at each end, your voice channel may be compromised by a man-in-the-middle attack (MitM). </p> <p> If the SAS is equal at both ends, then you should confirm it by clicking this padlock for stronger security of future calls to the same destination. For subsequent calls to the same destination, you don't have to confirm the SAS again. The padlock will show a check symbol when the SAS has been confirmed. </p> <p> Le clavier indique que votre voix est encrypté sur le réseau. </p> <h3>SAS - Short Authentication String (chaine d'authentification courte)</h3> <p> Les deux correspondants d'une ligne encrypté reçoivent le même SAS lors du premier appel. Si le SAS est différent, votre ligne est compromise. </p> <p> Si le SAS est égal, vous devez le confirmer en cliquant sur le clavier pour une meilleure sécurité des futurs appels vers cette même destination. Vous n'aurez plus à confirmer le SAS pour cette même destination. Le clavier affichera un symbol de confirmation quand le SAS est confirmé. </p> sas No need to translate Short authentication string SAS - Short authentication string g711a/g711a No need to translate Audio codec 0:00:00 Call duration Durée de l'appel sip:from No need to translate sip:to No need to translate subject No need to translate photo No need to translate Line &2: Ligne &2: Alt+2 Click to switch to line 2. Cliquez pour basculer sur la ligne 2. &File &Fichier &Edit &Edition C&all &Appel Activate line Activer la ligne &Registration &Connexion &Services &Services &View &Vue &Help Ai&de Call Toolbar Barre d'outil d'appel Quit Quitter &Quit &Quitter Ctrl+Q About Twinkle A propos de Twinkle &About Twinkle A propos de &Twinkle Call someone Appeler F5 Answer incoming call Répondre à l'apppel entrant F6 Release call Raccrocher Reject incoming call Rejeter l'appel entrant F8 Put a call on hold, or retrieve a held call Mettre un appel en attente, ou reprendre un appel en attente Redirect incoming call without answering Redirection d'appel entrant sans répondre Open keypad to enter digits for voice menu's Ouvrir le clavier pour siasir des digits du menu vocal Register Se connecter &Register Se &connecter Deregister Se déconnecter &Deregister Se &déconnecté Deregister this device Déconnecter cette interface Show registrations Montrer les connexions &Show registrations &Montrer les connexions Terminal capabilities Possibilités du terminal Request terminal capabilities from someone Demande les capacités du terminal d'un correspondant Do not disturb Ne pas déranger &Do not disturb Ne pas &déranger Call redirection Redirection d'appel Call &redirection... &Redirection d'appel... Repeat last call Wahlwiederholung, wählt letzten Ruf erneut F12 About Qt A propos de Qt About &Qt &A propos de Qt User profile Profil utilisateur &User profile... Profil &utilisateur... Join two calls in a 3-way conference Joindre une conférence 3 parties Mute a call Rendre muet Transfer call Transfert d'appel System settings Paramètres système &System settings... Paramètres &système... Deregister all Tout déconnecter Deregister &all &Tout déconnecter Deregister all your registered devices Déconnecter toutes les interfaces connectées Auto answer Réponse automatique &Auto answer Réponse &automatique Log &Log... Call history Historique d'appel Call &history... &Historique d'appel... F9 Change user ... Changer d'utilisateur ... &Change user ... &Changer d'utilisateur ... Activate or de-activate users Activer ou désactiver des utilisateurs What's This? Qu'est-ce que c'est? What's &This? &Qu'est-ce que c'est? Shift+F1 Shift+F1 Line 1 Ligne 1 Line 2 Ligne 2 idle libre dialing numérotation attempting call, please wait appel en cours, merci de patienter incoming call appel entrant establishing call, please wait établissement de l'appel, merci de patienter established établi established (waiting for media) établi (attente de données) releasing call, please wait Raccrochage en cours, merci de patienter unknown state Etat inconnu Voice is encrypted Voix encryptée Click to confirm SAS. Cliquez pour confirmer SAS. Click to clear SAS verification. Cliquez pour annuler la vérification SAS. User: Utilisateur: Call: Appel: Registration status: Statut de la connexion: Registered Connecté Failed Echoué Not registered Non connecté No users are registered. Aucun utilisteur n'est connecté. Do not disturb active for: "Ne pas dérangé" activé pour: Redirection active for: Redirection activée pour: Auto answer active for: "Réponse automatique" activée pour: Do not disturb is not active. "Ne pas dérangé" n'est pas actif. Redirection is not active. La redirection n'est pas active. Auto answer is not active. la réponse automatique n'est pas active. You have no missed calls. Vous n'avez pas d'appel en absence. You missed 1 call. 1 appel en absence. You missed %1 calls. %1 appels en absence. Click to see call history for details. Cliquez pour plus de détails . Starting user profiles... Démarrage des profiles d'utilisateurs... The following profiles are both for user %1 Les profils suivant sont pour l'utilisateur %1 You can only run multiple profiles for different users. Voius pouvez seulement executer plusieurs profils pour différents utilisateurs. You have changed the SIP UDP port. This setting will only become active when you restart Twinkle. Vous avez chazngé le port UDP de SIP. Pour prendre en compte les modifications, il est nécessaire de redémarrer Twinkle. Esc Esc Transfer consultation Consultation du transfert Hide identity Cacher l'identité Click to show registrations. Cliquez pour montrer les connexions. %1 new, 1 old message %1 nouveau, 1 ancien message %1 new, %2 old messages %1 nouveau, %2 ancien messages 1 new message 1 nouveau message %1 new messages %1 nouveau messages 1 old message 1 ancien message %1 old messages %1 anciens messages Messages waiting Messages reçus No messages Pas de messages <b>Voice mail status:</b> <b>Statut de la boîte vocale:</b> Failure Echec Unknown Inconnu Click to access voice mail. Cliquez pour accéder à la boîte vocale. Click to activate/deactivate Cliquez pour activer/désactiver Click to activate Cliquez pour activer not provisioned non enregistré You must provision your voice mail address in your user profile, before you can access it. Vous devez enregistrer votre numéro de boîte vocale dans votre profil d'utilisateur avant de pouvoir y accéder. The line is busy. Cannot access voice mail. La ligne est occupée. Impossible d'accéder à la boîte vocale. The voice mail address %1 is an invalid address. Please provision a valid address in your user profile. Le numéro %1 de boîte vocale est invalide. Merci d'enregistrer un numéro valide dans votre profil d'utilisateur. Call toolbar text Appel &Call... call menu text &Appel... Answer toolbar text Répondre &Answer menu text &Répondre Bye toolbar text Fin &Bye menu text &Fin Reject toolbar text Refuser &Reject menu text R&efuser Hold toolbar text Attente &Hold menu text Atte&nte Redirect toolbar text Redirect R&edirect... menu text Re&direction... Dtmf toolbar text Dtmf &Dtmf... menu text Dt&mf... &Terminal capabilities... menu text Possibilités du &terminal... Redial toolbar text Rappeler &Redial menu text &Rappeler Conf toolbar text Conf &Conference menu text &Conférence Mute toolbar text Muet &Mute menu text M&uet Xfer toolbar text Transfert Trans&fer... menu text &Transfert... Voice mail Boîte vocale &Voice mail &Boîte vocale Access voice mail Accès boîte vocale F11 Buddy list Liste d'avatars &Message &Message Msg Msg Instant &message... &Message instantané... Instant message Message instantané &Call... &Appel... &Edit... &Editer... &Delete &Supprimer O&ffline Déc&onnecté &Online &Connecté &Change availability Mod&ifier la disponibilité &Add buddy... &Ajouter un avatar... Failed to save buddy list: %1 Echec de la sauvegarde de la liste d'avatars: %1 You can create a separate buddy list for each user profile. You can only see availability of your buddies and publish your own availability if your provider offers a presence server. Vous pouvez créer une liste d'avatar pour chaque profil. Vous ne pouvez voir la disponibilité de vos avatars et publier la votre que si votre fournisseur de service dispose d'un serveur de présence. &Buddy list &Liste d'avatars &Display &Affichage F10 Diamondcard Manual &Manual Sign up &Sign up... Recharge... Balance history... Call history... Admin center... Recharge Balance history Admin center Call Appel &Answer &Répondre Answer Répondre &Bye &Fin Bye Fin &Reject R&efuser Reject Refuser &Hold Atte&nte Hold Attente R&edirect... Re&direction... Redirect Redirect &Dtmf... Dt&mf... Dtmf Dtmf &Terminal capabilities... Possibilités du &terminal... &Redial &Rappeler Redial Rappeler &Conference &Conférence Conf Conf &Mute M&uet Mute Muet Trans&fer... &Transfert... Xfer Transfert NumberConversionForm Twinkle - Number conversion Twinkle - Conversion de numéro &Match expression: &Expression de recherche: &Replace: &Remplacer: Perl style format string for the replacement number. Chaine au format Perl pour le remplacement du numéro. Perl style regular expression matching the number format you want to modify. Expression réguliuère au format Perl pour la vérification du format du numéro que vous voulez modifier. &OK Alt+O &Cancel Annuler (Es&c) Alt+C Match expression may not be empty. L'expression de vérification ne doit pas être vide. Replace value may not be empty. La valeur de remplacement ne doit pas être vide. Invalid regular expression. Expression régulière invalide. RedirectForm Twinkle - Redirect Twinkle - Redirection Redirect incoming call to Rediriger les appels entrants vers You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. Vous pouvez sélectionner jusqu'a 3 destinations vers lesquelles rediriger l'appel. Si la première destionationne répond pas, la deuxième sera essayée et ainsi de suite. &3rd choice destination: &3ème destination: &2nd choice destination: &2ème destination: &1st choice destination: &1ère destination: Address book Carnet d'adresses Select an address from the address book. Sélectionner une adresse du carnet d'adresses. &OK &Cancel Annuler (Es&c) F10 F12 F11 SelectNicForm Twinkle - Select NIC Twinkle - Selection de l'interface réseau Select the network interface/IP address that you want to use: Selectionnez l'interface réseau/adresse IP que vous voulez utiliser: You have multiple IP addresses. Here you must select which IP address should be used. This IP address will be used inside the SIP messages. Vous avez plusieurs adresses IP. Vous devez sélectionner ici l'adresse IP qui doit être utilisée. Cette adresse IP sera incluse dans les messages SIP. Set as default &IP Activer comme &IP par défaut Alt+I Make the selected IP address the default IP address. The next time you start Twinkle, this IP address will be automatically selected. Utiliser l'adresse IP sélectionnée comme adresse IP par défaut. La prochaine fois que vous démarrerez Twinkle, cette adresse IP sera automatiquement utilisée. Set as default &NIC Activer comme interface &réseau par défaut Alt+N Alt+R Make the selected network interface the default interface. The next time you start Twinkle, this interface will be automatically selected. Utiliser l'interface réseau sélectionné comme interface réseau par défaut. La prochaine fois que vous démarrerez Twinkle, cette interface sera automatiquement utilisée. &OK Alt+O If you want to remove or change the default at a later time, you can do that via the system settings. Si vous voulez supprimer ou modifier les paramètres par défaut, vous pourrez le faire par les paramètres système. SelectProfileForm Twinkle - Select user profile Twinkle -Sélectionner un profil utilisateur Select user profile(s) to run: Sélectionner le(s) profil(s) à démarrer: User profile Profil utilisateur Tick the check boxes of the user profiles that you want to run and press run. Cochez les cases des profils utilisateurs que vous voulez utilisez et appuyer sur "Démarrer". &New &Nouveau Alt+N Alt+N Create a new profile with the profile editor. Créer un nouveau profil avec l'éditeur de profil. &Wizard &Assistant Alt+W Alt+A Create a new profile with the wizard. Créer un nouveau profil avec l'assistant de création de profil. &Edit &Editer Alt+E Alt+E Edit the highlighted profile. Editer le profil sélectionné. &Delete &Supprimer Alt+D Alt+S Delete the highlighted profile. Supprimer le profil sélectionné. Ren&ame Ren&ommer Alt+A Alt+O Rename the highlighted profile. Renommer le profil sélectionné. &Set as default &Utiliser par défaut Alt+S Alt+U Make the selected profiles the default profiles. The next time you start Twinkle, these profiles will be automatically run. Utiliser le profil sélectionné comme profil par défaut. La prochaine fois que vous démarrerez Twinkle, ces profils seront automatiquement démarrés. &Run &Démarrer Alt+R Alt+D Run Twinkle with the selected profiles. Démarrer Twinkle avec le profil sélectionné. S&ystem settings Paramètres s&ystème Alt+Y Alt+Y Edit the system settings. Editer les paramètres système. &Cancel Annuler (Es&c) Alt+C Alt+C <html>Before you can use Twinkle, you must create a user profile.<br>Click OK to create a profile.</html> <html>Avant d'utiliser Twinkle, vous devez créer un profil utilisateur.<br>Cliquez sur OK pour créer un profil.</html> <html>You can use the profile editor to create a profile. With the profile editor you can change many settings to tune the SIP protocol, RTP and many other things.<br><br>Alternatively you can use the wizard to quickly setup a user profile. The wizard asks you only a few essential settings. If you create a user profile with the wizard you can still edit the full profile with the profile editor at a later time.<br><br>Choose what method you wish to use.</html> <html>Vous pouvez utiliser l'éditeur de profil pour créer un profil. Avec l'éditeur de profil vous pouvez modifier beaucoup de paramètres SIP, RTP et autres.<br><br>Vous pouvez également utiliser l'assistant pour créer un profil plus rapidement. Il vous proposera seulement quelques paramètres essentiels. Vous pourrez éditer ce profil plus tard en utilisant l'éditeur de profil.<br><br>Sélectionnez la méthode que vous préférez (débutants: l'assistant est conseillé).</html> <html>Next you may adjust the system settings. You can change these settings always at a later time.<br><br>Click OK to view and adjust the system settings.</html> <html>Vous devrez ajuster les paramètres systèmes. Vous pouvez changer ces parmètres à tout moment.<br><br>Cliquez sur OK pour voir et modifier les paramètres systèmes</html> You did not select any user profile to run. Please select a profile. Vous n'avez sélectionné aucun profil à démarrer. Merci de sélectionner un profil. Are you sure you want to delete profile '%1'? Etes-vous sûr de vouloir supprimer le profil '%1' ? Delete profile Supprimer un profil Failed to delete profile. Echec de la suppression de profil. Failed to rename profile. Echec du changement de nom de profil. <p>If you want to remove or change the default at a later time, you can do that via the system settings.</p> <p>Si vous voulez supprimer ou modifier le profil par défaut, vous pourrez le faire par les paramètres système. </p> Cannot find .twinkle directory in your home directory. Impossible de trouver le dossier .twinkle dans le dossier home. &Profile editor Editeur de &profil Create profile Ed&itor Alt+I Alt+E Dia&mondcard Alt+M Alt+A Modify profile Startup profile &Diamondcard Create a profile for a Diamondcard account. With a Diamondcard account you can make worldwide calls to regular and cell phones and send SMS messages. <html>You can use the profile editor to create a profile. With the profile editor you can change many settings to tune the SIP protocol, RTP and many other things.<br><br>Alternatively you can use the wizard to quickly setup a user profile. The wizard asks you only a few essential settings. If you create a user profile with the wizard you can still edit the full profile with the profile editor at a later time.<br><br> You can create a Diamondcard account to make worldwide calls to regular and cell phones and send SMS messages.<br><br> Choose what method you wish to use.</html> SelectUserForm Twinkle - Select user Twinkle - Selection d'utilisateur &Cancel Annuler (Es&c) Alt+C &Select all Tout &sélectionner Alt+S Alt+A &OK Alt+O C&lear all Tout &désélectionner Alt+L purpose No need to translate User Utilisateur Register Se connecter Select users that you want to register. Sélectionnez le profil que vous voulez connecter. Deregister Se déconnecter Select users that you want to deregister. Sélectionnez le profil que vous voulez déconnecter. Deregister all devices Déconnecter toutes les interfaces Select users for which you want to deregister all devices. Sélectionnez le profil dont vous voulez déconnecter toutes les interfaces. Do not disturb Ne pas déranger Select users for which you want to enable 'do not disturb'. Sélectionnez le profil dont vous voulez activer "ne pas déranger". Auto answer Réponse automatique Select users for which you want to enable 'auto answer'. Sélectionnez le profil dont vous voulez activer la réponse automatique. SendFileForm Twinkle - Send File Twinkle - Envoi de fichier Select file to send. Choisir le fichier à envoyer. &File: &Fichier: &Subject: &Sujet: &OK &OK Alt+O &Cancel Annuler (Es&c) Alt+C File does not exist. Le fichier n'existe pas. Send file... Envoi de fichier... SrvRedirectForm Twinkle - Call Redirection Twinkle - Redirection d'appel User: Utilisateur: There are 3 redirect services:<p> <b>Unconditional:</b> redirect all calls </p> <p> <b>Busy:</b> redirect a call if both lines are busy </p> <p> <b>No answer:</b> redirect a call when the no-answer timer expires </p> Il y a 3 srvices de redirection:<p> <b>Inconditionnel:</b> tous les appels sont redirigés<p> <p> <b>Occupé:</b> Rediriger l'appel si les 2 lignes sont occupées </p> <p> <b>Pas de réponse:</b> redirige un appel quand le décompte "pas de réponse" expire. </p> &Unconditional &Inconditionnel &Redirect all calls &Rediriger tous les appels Alt+R Alt-A Activate the unconditional redirection service. Active le service de redirection inconditionelle. Redirect to Rediriger vers You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. Vous pouvez sélectionner jusqu'a 3 destinations vers lesquelles rediriger l'appel. Si la première destionationne répond pas, la deuxième sera essayée et ainsi de suite. &3rd choice destination: &3ème destination: &2nd choice destination: &2ème destination: &1st choice destination: &1ère destination: Address book Carnet d'adresses Select an address from the address book. Sélectionner une adresse du carnet d'adresses. &Busy &Occupé &Redirect calls when I am busy &Rediriger les appels quand je suis occupé Activate the redirection when busy service. Active le service de redirection "occupé". &No answer &Pas de réponse &Redirect calls when I do not answer &Rediriger les appels quand je ne réponds pas Activate the redirection on no answer service. Active le service de redirection sans réponse. &OK Alt+O Accept and save all changes. Accepter et enregistrer les modifications. &Cancel Annuler (Es&c) Alt+C Undo your changes and close the window. Annuler tous les changements et fermer la fenêtre. You have entered an invalid destination. Vous avez saisi une destination invalide. F10 F11 F12 SysSettingsForm Twinkle - System Settings Twinkle - Paramètres du système General Général Audio Audio Ring tones Sonneries Address book Carnet d'adresses Network Réseau Log Log Select a category for which you want to see or modify the settings. Sélectionnez la catégorie dont vous voulez voir ou modifier les paramètres. Sound Card Carte son Select the sound card for playing the ring tone for incoming calls. Sélectionnez la carte son qui jouera la sonnerie des appels entrants. Select the sound card to which your microphone is connected. Sélectionnez la carte son à laquelle est connecté le microphone. Select the sound card for the speaker function during a call. Sélectionnez la carte son à laquelle est connecté le haut parleur. &Speaker: &Haut-parleur: &Ring tone: &Sonnerie: Other device: Autre interface: &Microphone: &Microphone: When using ALSA, it is not recommended to use the default device for the microphone as it gives poor sound quality. Avec ALSA, il n'est pas recommandé d'utiliser l'interface par défaut pour le microphone car ceci dégrade la qualité du son. Reduce &noise from the microphone Réduire le &bruit du microphone Alt+N Alt+B Recordings from the microphone can contain noise. This could be annoying to the person on the other side of your call. This option removes soft noise coming from the microphone. The noise reduction algorithm is very simplistic. Sound is captured as 16 bits signed linear PCM samples. All samples between -50 and 50 are truncated to 0. L'enregistrement par l'intermédiaire d'un microphone peut contenir du bruit. Ceci peut être génant pour le correspondant. Cette option supprime ce bruit. L'algoritme de réduction de bruit est très simple. Le son est enregistré en échantillons PCM 16 bits linéaires signés. Tous les échantillons entre -50 et 50 sont ramenés à zéro. Advanced Avancé OSS &fragment size: OSS taille du &fragment: 16 32 64 128 256 The ALSA play period size influences the real time behaviour of your soundcard for playing sound. If your sound frequently drops while using ALSA, you might try a different value here. La période de lecture ALSA influence le comportement en temps réel de votre carte son. Si votre son disparait fréquemment en utilisant ALSA, vous devriez essayer différentes valeurs. ALSA &play period size: ALSA période de &lecture (LS): &ALSA capture period size: ALSA période de &capture (MIC): The OSS fragment size influences the real time behaviour of your soundcard. If your sound frequently drops while using OSS, you might try a different value here. La taille du fragment OSS influence le comportement en temps réel de votre carte son. Si votre son disparait fréquemment en utilisant OSS, vous devriez essayer différentes valeurs. The ALSA capture period size influences the real time behaviour of your soundcard for capturing sound. If the other side of your call complains about frequently dropping sound, you might try a different value here. La période de capture ALSA influence le comportement en temps réel de votre carte son. Si votre son disparait fréquemment en utilisant ALSA, vous devriez essayer différentes valeurs. &Max log size: Taille &max. du fichier de log: The maximum size of a log file in MB. When the log file exceeds this size, a backup of the log file is created and the current log file is zapped. Only one backup log file will be kept. La taille maximum du fichier de log en Mo. Quand le fichier de log excède cette taille, une sauvegarde de ce fichier est effectué et le fichier est supprimé. Seulement un fichier de sauvegarde est conservé. MB Log &debug reports Sauvegarder les rapports de &debug Alt+D Indicates if reports marked as "debug" will be logged. Indique si les rapports marqués "débug" seront archivés. Log &SIP reports Sauvegarder les rapports &SIP Alt+S Indicates if SIP messages will be logged. Indique si les messages SIP douvent être sauvegardés. Log S&TUN reports Sauvegarder les rapports S&TUN Alt+T Indicates if STUN messages will be logged. Indique si les messages STUN douvent être sauvegardés. Log m&emory reports Sauvegarder les rapports &mémoire Alt+E Alt+M Indicates if reports concerning memory management will be logged. Indique si les rapports concernant la gestion mémoire seront archivés. System tray Tableau de bord Create &system tray icon on startup Créer une icone dans le &tableau de bord au démarrage Enable this option if you want a system tray icon for Twinkle. The system tray icon is created when you start Twinkle. Cochez cette case si vous voulez une icone dans le tableau de bord pour Twinkle. Cette icone est créée au démarrage de Twinkle. &Hide in system tray when closing main window &Rabattre dans le &tableau de bord à la fermeture de la fenêtre principale Alt+H Alt+R Enable this option if you want Twinkle to hide in the system tray when you close the main window. Cochez cette case si vous voulez rabattre Twinkle dans le tableau de bord quand vous fermez la fenêtre principale. Startup Démarrage Next time you start Twinkle, this IP address will be automatically selected. This is only useful when your computer has multiple and static IP addresses. La prochaine fois que vous lancerez Twinkle, cette adresse IP sera automatiquement sélectionnée. Ceci est utile uniquement si votre ordinateur à plusieurs IP statiques. Default &IP address: Adresse &IP par défaut: Next time you start Twinkle, the IP address of this network interface be automatically selected. This is only useful when your computer has multiple network devices. La prochaine fois que vous lancerez Twinkle, l'adresse IP de cette interface réseau sera automatiquement sélectionnée. Ceci est utile uniquement si votre ordinateur à plusieurs interfaces réseau. Default &network interface: &Réseau par défaut: S&tartup hidden in system tray &Démarrer rabattu dans le tableau de bord Next time you start Twinkle it will immediately hide in the system tray. This works best when you also select a default user profile. La prochaine fois que vous lancerez Twinkle, il sera immédiatement rabattu dans le tableau de bord. Ceci fonctionne mieux si vous sélectionnez également un utilisateur par défaut. Default user profiles Profil utilisateur par défaut If you always use the same profile(s), then you can mark these profiles as default here. The next time you start Twinkle, you will not be asked to select which profiles to run. The default profiles will automatically run. Si vous utilisez toujours le(s) même(s) profile(s), vous pouvez le(s) définir comme "défaut". La prochaine fois que vous lancerai Twinkle, vous n'aurez pas à sélectionner de profil. Les profils par défaut seront lancés automatiquement. Services Services Call &waiting Mise en &attente Alt+W Alt+A With call waiting an incoming call is accepted when only one line is busy. When you disable call waiting an incoming call will be rejected when one line is busy. Avec cette option, un appel entrant sera accepté si une ligne est occupée. Si vous désactivez la mise en attente d'appel, un appel entrant sera refusé quand une ligne est occupée. Hang up &both lines when ending a 3-way conference call. Raccrocher les deux lignes à la fin d'une &conférence. Alt+B Alt+C Hang up both lines when you press bye to end a 3-way conference call. When this option is disabled, only the active line will be hung up and you can continue talking with the party on the other line. Raccroche les deux lignes quand vous clickez sur le bouton "Fin" lors d'une conférence. Quand cette option est désactivée, seule la ligne active est raccrochée et vous pourvez continuer à parler avec le correspondante de l'autre ligne. &Maximum calls in call history: Nombre d'appels &maximum dans l'historique des appels: The maximum number of calls that will be kept in the call history. Le nombre d'appels maximum qui seront enregistrés dans l'historique des appels. &Auto show main window on incoming call after Affichage &automatiquement de la fenêtre principale lors d'un appel Alt+A When the main window is hidden, it will be automatically shown on an incoming call after the number of specified seconds. Quand la fenêtre principale est cachée, elle sera automatiqument affichée lors d'un appel entrant après le nombre de secondes spécifié. Number of seconds after which the main window should be shown. Délai d'affichage automatique sur appel entrant. secs secs The UDP port used for sending and receiving SIP messages. Le port UDP utilisé pour envoyer et recevoir des messages SIP. &RTP port: Port &RTP: The UDP port used for sending and receiving RTP for the first line. The UDP port for the second line is 2 higher. E.g. if port 8000 is used for the first line, then the second line uses port 8002. When you use call transfer then the next even port (eg. 8004) is also used. Le port UDP utilisé pour envoyer et recevoir des RTP pour la première ligne. Celui de la seconde est plus grand de 2. Ex: Si le port 8000 est utilisé pour la première ligne, celui de la seconde sera 8002. Quand vous utilisez le transfert d'appel, le port suivant (ex:8004) est alors également utilisé. &SIP UDP port: Port &SIP UDP : Ring tone Sonneries &Play ring tone on incoming call &Sonner lors d'un appel entrant Alt+P Alt+S Indicates if a ring tone should be played when a call comes in. Indique si une sonnerie doit être jouée lors d'un appel entrant. &Default ring tone Sonneries par &défaut Play the default ring tone when a call comes in. Joue la sonnerie par défaut lors d'un appel entrant. C&ustom ring tone Sonnerie &personnalisée Alt+U Alt+P Play a custom ring tone when a call comes in. Joue une sonnerie personnalisée lors d'un appel entrant. Specify the file name of a .wav file that you want to be played as ring tone. Saisissez le nom de fichier .wav que vous voulez utiliser comme sonnerie. Ring back tone Sonneries de tonalité P&lay ring back tone when network does not play ring back tone Jouer une& tonalité quand le réseau ne le fait pas Alt+L Alt+T <p> Play ring back tone while you are waiting for the far-end to answer your call. </p> <p> Depending on your SIP provider the network might provide ring back tone or an announcement. </p> <p>Joue une tonalité pendant que vous attendez que votre correspondant décroche. </p> <p>Suivant votre fournisseur de service, le réseau peut émettre une tonalité ou une annonce.</p> D&efault ring back tone &Tonalité par défaut Play the default ring back tone. Joue la tonalité par défaut. Cu&stom ring back tone Tonalité p&ersonnalisée Play a custom ring back tone. Joue la tonalité personnalisée. Specify the file name of a .wav file that you want to be played as ring back tone. Saisissez le nom de fichier .wav que vous voulez utiliser comme tonalité. &Lookup name for incoming call &Voir le nom de l'appel entrant Ove&rride received display name Ne pas &tenir compte du nom d'affichage reçu Alt+R Alt+T The caller may have provided a display name already. Tick this box if you want to override that name with the name you have in your address book. L'appelant peut avoir défini un nom d'affichage. Cochez cette case si vous voulez le remplacer par celui défini dans votre carnet d'adresses. Lookup &photo for incoming call Visionner la &photo pour l'appel entrant Lookup the photo of a caller in your address book and display it on an incoming call. Visionner la photo de l'appelant dans votre carnet d'adresses et l'afficher pour les appels entrants. &OK Alt+O Accept and save your changes. Accepter et enregistrer les modifications. &Cancel Annuler (Es&c) Alt+C Undo all your changes and close the window. Annuler tous les changements et fermer la fenêtre. none This is the 'none' in default IP address combo aucune none This is the 'none' in default network interface combo aucune Either choose a default IP address or a default network interface. Choisissez soit une adresse IP par défaut soit une interface de réseau. Ring tones Description of .wav files in file dialog Sonneries Choose ring tone Choisir une sonnerie Ring back tones Description of .wav files in file dialog Sonneries de tonalité Choose ring back tone Choisir une sonnerie de tonalité &Validate devices before usage &Valider les interfaces avant d'utiliser Alt+V Alt+V <p> Twinkle validates the audio devices before usage to avoid an established call without an audio channel. <p> On startup of Twinkle a warning is given if an audio device is inaccessible. <p> If before making a call, the microphone or speaker appears to be invalid, a warning is given and no call can be made. <p> If before answering a call, the microphone or speaker appears to be invalid, a warning is given and the call will not be answered. <p>Twinkle valide l'interface audio avant utilisation pour éviter d'établir un appel sans canal audio.</p> <p>Au démarrage de Twinkle, un avertissement vous prévient si l'interface audio est inaccessible.</p> <p>Si avant de faire un appel, le microphone ou le haut-parleur est invalide, un avertissement s'affiche et l'appel est bloqué.</p> <p>Si avant de répondre à un appel, le microphone ou le haut-parleur est invalide, un avertissement s'affiche et il est impossible de répondre à un appel. On an incoming call, Twinkle will try to find the name belonging to the incoming SIP address in your address book. This name will be displayed. Lors d'un appel entrant, Twinkle essaiera de trouve le nom correspondant à l'adresse SIP dans votre carnet d'adresses. Ce nom sera affiché. Select ring tone file. Sélectionner un fichier de sonnerie. Select ring back tone file. Sélectionnez votre sonnerie de tonalité. Maximum allowed size (0-65535) in bytes of an incoming SIP message over UDP. Taille maximum allouée pour un message SIP entrant en UDP en octets (0-65535). &SIP port: Port &SIP: Max. SIP message size (&TCP): Taille max. du message SIP (&TCP): The UDP/TCP port used for sending and receiving SIP messages. Le port UDP/TCP utilisé pour envoyer et recevoir des messages SIP. Max. SIP message size (&UDP): Taille max. du message SIP (&UDP): Maximum allowed size (0-4294967295) in bytes of an incoming SIP message over TCP. Taille maximum allouée pour un message SIP entrant en TCP en octets (0-4294967295). W&eb browser command: Command to start your web browser. If you leave this field empty Twinkle will try to figure out your default web browser. 512 1024 Tip: for crackling sound with PulseAudio, set play period size to maximum. Enable in-call OSD SysTrayPopup Answer Répondre Reject Refuser Incoming Call TermCapForm Twinkle - Terminal Capabilities Twinkle - Capacités du terminal &From: &De: Get terminal capabilities of Récuperer les possibilités du terminal de &To: &à: The address that you want to query for capabilities (OPTION request). This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. L'adresse dont vous voulez demandes les possiblités (demande d'OPTIONS). Ceci peut être une adresse SIP comme <b>sip:exemple@exemple.com</b> ou uniquement la partie utilisateur ou le numéro de téléphone d'une adresse complète. Quand vous ne spécifiez pas une adresse complète, Twinkle complètera cette adresse en utilisant le domaine défini dans votre profil utilisateur. Address book Carnet d'adresses Select an address from the address book. Sélectionner une adresse du carnet d'adresses. &OK &Cancel Annuler (Es&c) F10 TransferForm Twinkle - Transfer Twinkle - Transfert Transfer call to Transférer l'appel vers &To: &à: The address of the person you want to transfer the call to. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. L'adresse de la personne vers que vous voulez transférer l'appel. Ceci peut être une adresse SIP comme <b>sip:exemple@exemple.com</b> ou uniquement la partie utilisateur ou le numéro de téléphone d'une adresse complète. Quand vous ne spécifiez pas une adresse complète, Twinkle complètera cette adresse en utilisant le domaine défini dans votre profil utilisateur. Address book Carnet d'adresses Select an address from the address book. Sélectionner une adresse du carnet d'adresses. &OK Alt+O &Cancel Annuler (Es&c) Type of transfer Type de transfert &Blind transfer Transfert &direct Alt+B Alt+D Transfer the call to a third party without contacting that third party yourself. Transfert l'appel vers un tier sans consultation. T&ransfer with consultation &Transfert avec consultation Alt+R Alt+T Before transferring the call to a third party, first consult the party yourself. Avant de transférer l'appel vous pourrez consulter le tier vous-même. Transfer to other &line Transfert vers l'autre &ligne Alt+L Connect the remote party on the active line with the remote party on the other line. Connecte le correspondant de la ligne active avec le correspondant de l'autre ligne. F10 TwinkleCore Failed to create log file %1 . Impossible de créer le fichier de log %1. Cannot open file for reading: %1 Impossible d'ouvrir le fichier %1 en lecture File system error while reading file %1 . Erreur système pendant la lecture du fichier %1. Cannot open file for writing: %1 Impossible d'ouvrir le fichier %1 en écriture File system error while writing file %1 . Erreur système pendant l'écriture du fichier %1. Excessive number of socket errors. Nombre trop important d'erreurs de socket. Built with support for: Compilé à l'aide de: Contributions: Contributions: This software contains the following software from 3rd parties: Ce logiciel contient les logiciels tiers suivants: * GSM codec from Jutta Degener and Carsten Bormann, University of Berlin * G.711/G.726 codecs from Sun Microsystems (public domain) * iLBC implementation from RFC 3951 (www.ilbcfreeware.org) * Parts of the STUN project at http://sourceforge.net/projects/stun * Parts of libsrv at http://libsrv.sourceforge.net/ For RTP the following dynamic libraries are linked: Pour RTP, les librairies dynamiques suivantes sont rattachées: Translated to english by <your name> Traduit en français par:<br> ©20070614 Olivier Aufrère Directory %1 does not exist. Le dossier %1 n'existe pas. Cannot open file %1 . Impossible d'ouvrir le fichier %1. %1 is not set to your home directory. %1 n'est pas paramêtré pour votre dossier home. Directory %1 (%2) does not exist. Le dossier %1 (%2) n'existe pas. Cannot create directory %1 . Impossible de créer le dossier %1. Lock file %1 already exist, but cannot be opened. Le fichier de vérouillage %1 existe déjà mais ne peut être ouvert. %1 is already running. Lock file %2 already exists. %1 en cours d'execution Le fichier de vérouillage %2 existe déjà. Cannot create %1 . Impossible de créer %1. Cannot write to %1 . Impossible d'écrire su %1. Syntax error in file %1 . Erreur de syntaxe dans le fichier %1. Failed to backup %1 to %2 Impossible de sauvegarder %1 sur %2 unknown name (device is busy) nom inconnu (interface utilisée) Default device Interface par défaut Anonymous Anonyme Warning: Attention: Call transfer - %1 Transfert d'appel - %1 Sound card cannot be set to full duplex. La carte son ne peut pas être passée en full duplex. Cannot set buffer size on sound card. Impossible de paramètrer la taille du beffer de la carte son. Sound card cannot be set to %1 channels. Impossible de paramètrer la carte son pour les canaux %1. Cannot set sound card to 16 bits recording. Impossible de paramètrer la carte son en enregistrement 16 bits. Cannot set sound card to 16 bits playing. Impossible de paramètrer la carte son en lecture 16 bits. Cannot set sound card sample rate to %1 Impossible de paramètrer la carte son à un taux de sample %1 Opening ALSA driver failed Echec de l'ouverture d'ALSE Cannot open ALSA driver for PCM playback Impossible d'ouvrir ALSA pour la lecture PCM Cannot resolve STUN server: %1 Impossible de trouver le serveur STUN %1 You are behind a symmetric NAT. STUN will not work. Configure a public IP address in the user profile and create the following static bindings (UDP) in your NAT. Vous êtes derrière un NAT symétrique. STUN ne fonctionnera pas. Configurez une adresse IP publique pour votre profil utilisateur et créez les attaches statiques suivantes (UDP) dans votre NAT. public IP: %1 --> private IP: %2 (SIP signaling) IP publique: %1 -> IP privée: %2 (SIP) public IP: %1-%2 --> private IP: %3-%4 (RTP/RTCP) IP publique: %1-%2 -> IP privée: %3-%4 (RTP/RTCP) Cannot reach the STUN server: %1 Impossible d'atteindre le serveur STUN: %1 Port %1 (SIP signaling) Port %1 (SIP) NAT type discovery via STUN failed. Echec de l'identification du type NAT par STUN. If you are behind a firewall then you need to open the following UDP ports. Si vous êtes derrière un firemwall, vous devez ouvrir les ports UDP suivants. Ports %1-%2 (RTP/RTCP) Ports %1-%2 (RTP/RTCP) Cannot access the ring tone device (%1). Impossible d'accéder à l'interface %1 de la sonnerie. Cannot access the speaker (%1). Impossible d'accéder à l'interface %1 du haut-parleur. Cannot access the microphone (%1). Impossible d'accéder à l'interface %1 du microphone. Cannot open ALSA driver for PCM capture Impossible d'ouvrir ALSA pour la capture PCM Cannot receive incoming TCP connections. Impossible de recevoir des connexions TCP entrantes. Failed to create file %1 Echec de la création du fichier %1 Failed to write data to file %1 Echec de l'écriture de données dans %1 Failed to send message. Echec de l'envoi du message. Cannot lock %1 . UserProfileForm Twinkle - User Profile Twinkle - Profil utilisateur User profile: Profil utilisateur: Select which profile you want to edit. Sélectionnez le profil que vous voulez éditer. User Utilisateur SIP server Serveur SIP RTP audio RTP Audio SIP protocol Protocole SIP Address format Format d'adresse Timers Décomptes Ring tones Sonneries Scripts Security Sécurité Select a category for which you want to see or modify the settings. Sélectionnez la catégorie dont vous voulez voir ou modifier les paramètres. &OK Alt+O Accept and save your changes. Accepter et enregistrer les modifications. &Cancel Annuler (Es&c) Alt+C Undo all your changes and close the window. Annuler tous les changements et fermer la fenêtre. SIP account Compte SIP &User name*: Nom d'&utilisateur*: &Domain*: &Domaine*: Or&ganization: Or&ganisation: The SIP user name given to you by your provider. It is the user part in your SIP address, <b>username</b>@domain.com This could be a telephone number. <br><br> This field is mandatory. Le nom d'utilisateur SIP donné par votre fournisseur de service. C'est la partie "utilisateur" de votre adresse SIP, <b>utilisateur</b>@domain.com. Ceci peut être un numéro de téléphone. <br><br> Ce champ est obligatoire. The domain part of your SIP address, username@<b>domain.com</b>. Instead of a real domain this could also be the hostname or IP address of your <b>SIP proxy</b>. If you want direct IP phone to IP phone communications then you fill in the hostname or IP address of your computer. <br><br> This field is mandatory. Le domaine de votre adresse SIP, username@<b>domain.com</b>. A la place d'un vrai nom de domaine, il est également possible de saisir le nom de l'hôte ou l'adresse IP de votre <b>proxy SIP</b>. Si vous voulez uniquement des communications de PC à PC, saisissez le nom de l'hôte ou l'adresse IP de votre ordinateur. <br><br> Ce champ est obligatoire. You may fill in the name of your organization. When you make a call, this might be shown to the called party. Vous pouvez remplir le nom de votre organisation. Quand vous ferez un appel, ceci sera montré à votre correspondant. This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. C'est simplement votre nom complet, ex: Pierre Dupond. Il est utilisé pour l'affichage. Quand vous ferez un appel, ceci sera montré à votre correspondant. &Your name: &Votre nom: SIP authentication Authentification SIP &Realm: Authentication &name: &Nom d'authentification: &Password: Mot de &passe: The realm for authentication. This value must be provided by your SIP provider. If you leave this field empty, then Twinkle will try the user name and password for any realm that it will be challenged with. Le realm pour l'authentification. Cette valeur vous est fournie par votre fournisseur de service SIP. Si vous laissez ce champ vide, Twinkle utilisera le nom d'utilisateur et le mot de passe en cas de demande de realm. Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Votre nom d'authentification SIP. Souvent, c'est le même que votre nom d'utilisateur SIP. Cependant, il peut être différent. Your password for authentication. Votre mot de passe d'authentification. Registrar Registrar (Serveur proxy) &Registrar: &Registrar: The hostname, domain name or IP address of your registrar. If you use an outbound proxy that is the same as your registrar, then you may leave this field empty and only fill in the address of the outbound proxy. Le nom d'hôte, le nom de domaine ou l'adresse IP de votre fournisseur de service. Si vous utilisez un proxy sortant qui est identique à votre fournisseur de service, vous pouvez laisser ce champ vide et seulement remplir l'adresse du proxy sortant. &Expiry: &Expiration: The registration expiry time that Twinkle will request. Le délai d'expiration de l'établissement de la connexion par Twinkle. Standard: 3600 (=1h). seconds secondes Re&gister at startup Se &connecter au démarrage Alt+G Alt-C Indicates if Twinkle should automatically register when you run this user profile. You should disable this when you want to do direct IP phone to IP phone communication without a SIP proxy. Indique si Twinkle doit automatiquement se connecter quand cous utilisez ce profil. Vous devrez désactivé ceci en cas de communication directe de téléphone IP à téléphone IP sans passer par proxy SIP. Outbound Proxy Proxy sortant &Use outbound proxy &Utiliser le proxy sortant Alt+U Indicates if Twinkle should use an outbound proxy. If an outbound proxy is used then all SIP requests are sent to this proxy. Without an outbound proxy, Twinkle will try to resolve the SIP address that you type for a call invitation for example to an IP address and send the SIP request there. Indique si Twinkle doit utiliser un proxy sortant. Si un proxy sortant est utiliser, toutes les demandes SIP sont envoyées par ce proxy. Sans proxy sortant, Twinkle essaiera de résoudre l'adresse SIP que vous avez saisie pour une invitation d'appel, par exemple à une adresse IP et envoie la demande SIP à cette adresse. Outbound &proxy: &Proxy sortant: &Send in-dialog requests to proxy &Envoyer des demandes "in-dialog" au proxy Alt+S Alt+E SIP requests within a SIP dialog are normally sent to the address in the contact-headers exchanged during call setup. If you tick this box, that address is ignored and in-dialog request are also sent to the outbound proxy. Les demandes SIP incluses dans un dialogue SIP sont normalement envoyées dans les entêtes de contact échangées pendant l'établissement de l'appel. Si vous cochez cette case, cette adresse est ignorée et les demandes incluses dans le dialog sont également envoyées par le proxy de sortie. &Don't send a request to proxy if its destination can be resolved locally. &Ne pas envoyer une demande au proxy si la destination peut être trouvée locallement. Alt+D Alt+N When you tick this option Twinkle will first try to resolve a SIP address to an IP address itself. If it can, then the SIP request will be sent there. Only when it cannot resolve the address, it will send the SIP request to the proxy (note that an in-dialog request will only be sent to the proxy in this case when you also ticked the previous option.) Quand vous cochez cette case, Twinkle essaiera d'abord de trouver une adresse SIP par une adresse IP. S'il y arrive, la demande SIP sera envoyée par ce mode. Dans le cas contraire, il enverra la demande SIP au proxy (nota: une demande incluse dans un dialogue serra uniquement envoyée au proxy si vous avez également cochez la case précédente) The hostname, domain name or IP address of your outbound proxy. Le nom d l'hôte, le nom de domaine ou l'adresse IP de votre proxy de sortie. Co&decs Codecs Available codecs: Codecs disponibles: G.711 A-law G.711 u-law GSM speex-nb (8 kHz) speex-wb (16 kHz) speex-uwb (32 kHz) List of available codecs. Liste des codecs disponibles. Move a codec from the list of available codecs to the list of active codecs. Active un codec. Move a codec from the list of active codecs to the list of available codecs. Désactive un codec. Active codecs: Codecs actifs: List of active codecs. These are the codecs that will be used for media negotiation during call setup. The order of the codecs is the order of preference of use. Liste des codecs actifs. Ceux sont les codecs qui seront utilisés pour la négociation lors de l'établissement de l'appel. L'ordre des codecs est l'ordre de préférence d'utilisation. Move a codec upwards in the list of active codecs, i.e. increase its preference of use. Monte un codec dans la liste des codecs actifs, i.e. augmente sa priorité d'utilisation. Move a codec downwards in the list of active codecs, i.e. decrease its preference of use. Descend un codec dans la liste des codecs actifs, i.e. diminue sa priorité d'utilisation. &G.711/G.726 payload size: &G.711/G.726 taille des données: The preferred payload size for the G.711 and G.726 codecs. La taille des données préférée pour les codecs G.711 et G.726. ms &iLBC iLBC i&LBC payload type: i&LBC Type des données: iLBC &payload size (ms): iLBC &taille des données (ms): The dynamic type value (96 or higher) to be used for iLBC. Le type de valeur dynamique (96 ou plus) devant être utilisé. 20 30 The preferred payload size for iLBC. Taille préférée des données du paquet RTP pour iLBC. &Speex Speex Perceptual &enhancement &Amélioration de la perception Alt+E Alt+A Perceptual enhancement is a part of the decoder which, when turned on, tries to reduce (the perception of) the noise produced by the coding/decoding process. In most cases, perceptual enhancement make the sound further from the original objectively (if you use SNR), but in the end it still sounds better (subjective improvement). l' "amélioration de la perception" (anglais: perceptual enhancement) est une partie du décodeur qui, quand elle est activée, essaie de réduire la perception du bruit produit par le décodage/encodage. Dans la plupart des cas, ceci rend le son assez différent de l'original, mais le rend plus agréable. &Ultra wide band payload type: Type des données pour la bande &Ultra-Large: Alt+V When enabled, voice activity detection detects whether the audio being encoded is speech or silence/background noise. VAD is always implicitly activated when encoding in VBR, so the option is only useful in non-VBR operation. In this case, Speex detects non-speech periods and encode them with just enough bits to reproduce the background noise. This is called "comfort noise generation" (CNG). En la sélectionnant, la détection de la parole (ie: voice activity detection ou VAD) détecte si le son encodé est de la voix ou du silence (bruit de fond). VAD est toujours implicitement activé en encodage VBR, cette option est donc uniquement utilisable pour les opérations non-VBR. Dans ce cas, Speex detecte les passages sans paroles et les encode avec juste le nombre de bits nécessaire pour reproduire le bruit de fond. Ceci est appelé la "génération de bruit pour le confort" (comfort noise generation CNG). &Wide band payload type: Type des données pour la bande &Large: Alt+B Variable bit-rate (VBR) allows a codec to change its bit-rate dynamically to adapt to the "difficulty" of the audio being encoded. In the example of Speex, sounds like vowels and high-energy transients require a higher bit-rate to achieve good quality, while fricatives (e.g. s,f sounds) can be coded adequately with less bits. For this reason, VBR can achieve a lower bit-rate for the same quality, or a better quality for a certain bit-rate. Despite its advantages, VBR has two main drawbacks: first, by only specifying quality, there's no guarantee about the final average bit-rate. Second, for some real-time applications like voice over IP (VoIP), what counts is the maximum bit-rate, which must be low enough for the communication channel. Variable Bit-Rate (VBR) permet au codec d'ajuster sa bande passante dynamiquement à la difficulté d'encodage du son. Ceci permet à qualité constante, de réduire la bande passante nécessaire. Il y a cependant des désavantages: en ne spécifiant que la qualité, il n'y à aucune garantie sur la bande passante moyenne finallement utilisée; pour certaines applications comme la VOIP, ce qui compte c'est la bande passante maximum, qui doit être la plus basse possible. The dynamic type value (96 or higher) to be used for speex wide band. La valeur dynamique (96 ou plus) à utiliser pour le speex à large bande (RFC 2833). Co&mplexity: Co&mplexité: Discontinuous transmission is an addition to VAD/VBR operation, that allows one to stop transmitting completely when the background noise is stationary. La transmission discontinue est un ajout à VAD/VBR, qui permet d'arrêter totalement la transmission quand le bruit de fond est stationnaire. The dynamic type value (96 or higher) to be used for speex narrow band. La valeur dynamique (96 ou plus) à utiliser pour le speex à petite bande (RFC 2833). With Speex, it is possible to vary the complexity allowed for the encoder. This is done by controlling how the search is performed with an integer ranging from 1 to 10 in a way that's similar to the -1 to -9 options to gzip and bzip2 compression utilities. For normal use, the noise level at complexity 1 is between 1 and 2 dB higher than at complexity 10, but the CPU requirements for complexity 10 is about 5 times higher than for complexity 1. In practice, the best trade-off is between complexity 2 and 4, though higher settings are often useful when encoding non-speech sounds like DTMF tones. Avec Spexx, il est possible de faire varier le taux de compression de l'encodeur. Ceci est possible en contrôlant comment la recherche est assurée avec un entier entre 1 et 10 d'une manière similaire aux option -1 à -9 de gzip et bzip2. En utilisation normale, le niveau de bruit au taux 1 est entre 1 et 2 dB plus élevé que au taux 10, mais l'utilisation du CPU au taux 10 est 5 fois plus grande que au taux 1. En pratique, Le meilleur compromis est entre 2 et 4, alors que des taux plus élevés sont souvent utilent pour encoder des sons autre que la voix comme les sonneries DTMF. &Narrow band payload type: Type des données pour la bande &Courte: G.726 G.726 &40 kbps payload type: G.726 &40 kb/s Type des données: The dynamic type value (96 or higher) to be used for G.726 40 kbps. La valeur dynamique (96 ou plus) a utiliser pour G.726 40 kb/s. The dynamic type value (96 or higher) to be used for G.726 32 kbps. La valeur dynamique (96 ou plus) a utiliser pour G.726 32 kb/s. G.726 &24 kbps payload type: G.726 &24 kb/s Type des données: The dynamic type value (96 or higher) to be used for G.726 24 kbps. La valeur dynamique (96 ou plus) a utiliser pour G.726 24 kb/s. G.726 &32 kbps payload type: G.726 &32 kb/s Type des données: The dynamic type value (96 or higher) to be used for G.726 16 kbps. La valeur dynamique (96 ou plus) a utiliser pour G.726 16 kb/s. G.726 &16 kbps payload type: G.726 &16 kb/s Type des données: DT&MF DTMF The dynamic type value (96 or higher) to be used for DTMF events (RFC 2833). La valeur dynamique (96 ou plus) à utiliser pour les évenements DTMF (RFC 2833). DTMF vo&lume: &Volume DTMF: The power level of the DTMF tone in dB. Le volume de la sonnerie DTMF en dB. The pause after a DTMF tone. La durée de la pose après la sonnerie DTMF. DTMF &duration: &Durée DTMF: DTMF payload &type: D&TMF Type des données: DTMF &pause: &Pause DTMF: dB Duration of a DTMF tone. Durée d'une sonnerie DTMF. DTMF t&ransport: &Méthode DTMF: Auto RFC 2833 Inband Out-of-band (SIP INFO) <h2>RFC 2833</h2> <p>Send DTMF tones as RFC 2833 telephone events.</p> <h2>Inband</h2> <p>Send DTMF inband.</p> <h2>Auto</h2> <p>If the far end of your call supports RFC 2833, then a DTMF tone will be send as RFC 2833 telephone event, otherwise it will be sent inband. </p> <h2>Out-of-band (SIP INFO)</h2> <p> Send DTMF out-of-band via a SIP INFO request. </p> <p><h3>RFC 2833</h3> Envoi une sonnerie DTMF comme un événement de téléphone (RFC 2833).</p> <p><h3>Inband</h3> Sende DTMF inband (tatsächliche Töne, die Twinkle ins Tonsignal einmischt).</p> <p><h3>Auto</h3> Wenn die Gegenstelle RFC 2833 unterstützt, dann DTMF-Töne als RFC 2833 telephone events senden, ansonsten inband.</p> <p><h3>Out-of-band (SIP INFO)</h3> Sende DTMF nur out-of-band via SIP INFO request.</p> General Général Redirection Redirection &Allow redirection &Authoriser la redirection Alt+A Indicates if Twinkle should redirect a request if a 3XX response is received. Indique si Twinkle doit rediriger une demande en cas de réception d'une réponse 3XX. Ask user &permission to redirect Demander la &permission de l'utilisateur avant de rediriger Alt+P Indicates if Twinkle should ask the user before redirecting a request when a 3XX response is received. Indique si Twinkle doit demander à l'utilisateur avant de rediriger une demande en cas de réception d'une réponse 3XX. Max re&directions: Max. re&directions: The number of redirect addresses that Twinkle tries at a maximum before it gives up redirecting a request. This prevents a request from getting redirected forever. Le nombre maximum de tentatives de redirection d'adresse avant l'abandon. Ceci évite qu'une demande ne soit redirigé indéfiniement. Protocol options Options du protocole Call &Hold variant: Variante d'&attente d'appel: RFC 2543 RFC 3264 Indicates if RFC 2543 (set media IP address in SDP to 0.0.0.0) or RFC 3264 (use direction attributes in SDP) is used to put a call on-hold. Indique si RFC 2543 (passer l'adresse IP de l'interface dans SDP à 0.0.0.0) ou RFC 3264 (utiliser les attributs de direction dans SDP) est utilisé pour mettre un appel en attente. Allow m&issing Contact header in 200 OK on REGISTER Authorise l'absence de l'&entête de contact dans 200 OK lors de la connexion Alt+I Alt+E A 200 OK response on a REGISTER request must contain a Contact header. Some registrars however, do not include a Contact header or include a wrong Contact header. This option allows for such a deviation from the specs. Une réponse "200 OK" lors d'une connexion, doit comporter une entête de contact.Cependant, quelques fournisseurs de service, n'incorporent pas d''entête de contact ou en incorpore une erronée. Cette option authorise cette transgression. &Max-Forwards header is mandatory &Max-Forwards-Header onbligatoire Alt+M According to RFC 3261 the Max-Forwards header is mandatory. But many implementations do not send this header. If you tick this box, Twinkle will reject a SIP request if Max-Forwards is missing. Selon le RFC3261, l'entête Max-Forwards est obligatoire, mais plusieurs implémentations n'envoient pas cet entête. Si vous cochez cette case, Twinkle rejettera une demande SIP si Max-Forwards est manquant. Put &registration expiry time in contact header Mettre le délai d'expi&ration de la connexion dans l'entête de contact Alt+R In a REGISTER message the expiry time for registration can be put in the Contact header or in the Expires header. If you tick this box it will be put in the Contact header, otherwise it goes in the Expires header. Dans un message REGISTER, le temps d'expiration de la connexion peut être inséré dans l'entête de contact ou dans celle d'expiration. Si vous cochez cette case, il sera inséreé dans celle de contact, dans le cas contraire, dans celle d'expiration. &Use compact header names &Utiliser les nom d'entêtes courts Indicates if compact header names should be used for headers that have a compact form. Indique si un nom d'entête court doit être utilisé pour les entêtes courtes. Allow SDP change during call setup Authoriser les modification SDP pendant l'établissement d'un appel <p>A SIP UAS may send SDP in a 1XX response for early media, e.g. ringing tone. When the call is answered the SIP UAS should send the same SDP in the 200 OK response according to RFC 3261. Once SDP has been received, SDP in subsequent responses should be discarded.</p> <p>By allowing SDP to change during call setup, Twinkle will not discard SDP in subsequent responses and modify the media stream if the SDP is changed. When the SDP in a response is changed, it must have a new version number in the o= line.</p> <p>Une UAS SIP doit envoyer un SDP dans une réponse 1XX. Quand l'appel est répondu, l'UAS SIP doit envoyer le même SDP dans une réponse 200 OK selon le RFC 3261. Après réception du SDP, les SDP dans les réponses suivantes seront annulés.</p> <p>En authorisant les modifications de SDP pendant l'appel, Twinkle n'annulera pas les SDP dans les réponses suivantes et modifiera le medium si le SDP a changé.</p> <p> Twinkle creates a unique contact header value by combining the SIP user name and domain: </p> <p> <tt>&nbsp;user_domain@local_ip</tt> </p> <p> This way 2 user profiles, having the same user name but different domain names, have unique contact addresses and hence can be activated simultaneously. </p> <p> Some proxies do not handle a contact header value like this. You can disable this option to get a contact header value like this: </p> <p> <tt>&nbsp;user@local_ip</tt> </p> <p> This format is what most SIP phones use. </p> <p>Twinkle crée une entête de contact unique en combinant le nom d'utilisateur et de domaine SIP: <br> <tt>&nbsp;user_domain@local_ip</tt> </p> <p> De cette façon, 2 profils utilisateur ayant le même nom d'utilisateur mais des domaines différents, ont une adresse unique de contact et peuvent ainsi être activé simultanément. </p> <p> Des proxy ne construisent pas l'entête de contact de cette façon. Vous pouvez désactiver cette option pour avoir une entête comme ceci: <br> <tt>&nbsp;user@local_ip</tt> </p> <p> Ce format est utiliser par la plupart des téléphones SIP. </p> &Encode Via, Route, Record-Route as list &Encode Via, Route, Record-Route comme une liste The Via, Route and Record-Route headers can be encoded as a list of comma separated values or as multiple occurrences of the same header. Les entêtes Via-, Route- et Record-Route peuvent être encodé comme une liste séparée par des virgules ou comme des occurrences mulitples de la même entête. SIP extensions extensions SIP &100 rel (PRACK): disabled désactivé supported supporté required requis preferred préféré Indicates if the 100rel extension (PRACK) is supported:<br><br> <b>disabled</b>: 100rel extension is disabled <br><br> <b>supported</b>: 100rel is supported (it is added in the supported header of an outgoing INVITE). A far-end can now require a PRACK on a 1xx response. <br><br> <b>required</b>: 100rel is required (it is put in the require header of an outgoing INVITE). If an incoming INVITE indicates that it supports 100rel, then Twinkle will require a PRACK when sending a 1xx response. A call will fail when the far-end does not support 100rel. <br><br> <b>preferred</b>: Similar to required, but if a call fails because the far-end indicates it does not support 100rel (420 response) then the call will be re-attempted without the 100rel requirement. Indique si l'extension 100rel (PRACK) est supportée:<br><br> <b>désactivée</b>: extension 100rel désactivée <br><br> <b>supportée</b>: 100rel supportée (elle est insérée dans "supported header" d'une INVITE sortante). Le correspondant peut alors exiger un PRACK dans une réponse 1xx. <br><br> <b>requise</b>: 100rel requise (elle est insérée dans "equire header" d'une INVITE sortante). Si une INVITE entrante indique qu'elle supporte 100rel, alors Twinkle exigera un PRACK lors de l'envoi d'une reponse 1xx. Si le correspondant ne supporte pas 100rel, l'appel ne se réalise pas. <br><br> <b>préférée</b>: proche de requise, mais si l'appel échoue suite à l'absence de support de 100rel par le correspondant (420 réponses), une nouvelle tentativce d'appel sans 100rel sera lancée. REFER Call transfer (REFER) Transfert d'appel (REFER) Allow call &transfer (incoming REFER) Authoriser le &transfert d'appel (REFER entrant) Alt+T Indicates if Twinkle should transfer a call if a REFER request is received. Indique si Twinkle doit transferer l'appel en cas de réception d'une demande REFER. As&k user permission to transfer Demander l'aut&horisation d'utilisateur avant de transférer Alt+K Alt+H Indicates if Twinkle should ask the user before transferring a call when a REFER request is received. Indique si Twinkle doit demander à l'utilisateur avant de transférer un appel en cas de réception d'une demande REFER. Hold call &with referrer while setting up call to transfer target Mettre l'appel avec un &mandataire en attente pendant l'établissement de l'appel vers le destinataire du transfert Alt+W Alt+M Indicates if Twinkle should put the current call on hold when a REFER request to transfer a call is received. Indique si Twinkle doit demander à l'utilisateur avant de mettre l'appel en attente en cas de réception d'une réponse REFER pour un transfert. Ho&ld call with referee before sending REFER Mettre l'appel avec un ma&ndant en attente avant d'envoyer un REFER Alt+L Alt+N Indicates if Twinkle should put the current call on hold when you transfer a call. Indique si twinkle doit mettre l'appel courant en attente pendant un transfert. Auto re&fresh subscription to refer event while call transfer is not finished Ra&fraichir la subsciption au refer automatiquement avant que le transfert d'appel ne soit fini Alt+F While a call is being transferred, the referee sends NOTIFY messages to the referrer about the progress of the transfer. These messages are only sent for a short interval which length is determined by the referee. If you tick this box, the referrer will automatically send a SUBSCRIBE to lengthen this interval if it is about to expire and the transfer has not yet been completed. Quand l'appel est transféré, le mandant envoie des messages NOTIFY au mandataire à propos de la progression du transfert. Ces messages sont seulement envoyés pendante une courte période dans la durée est déterminée par le mandant. Si vous cochez cette case, le mandataire enverra automatiquement un SUBSCRIBE pour allonger cette durée si elle est trop courte (transfert non terminé). NAT traversal NAT Tranvsersal &NAT traversal not needed &NAT traversal non nécessaire Alt+N Choose this option when there is no NAT device between you and your SIP proxy or when your SIP provider offers hosted NAT traversal. Cochez cette case quand il n'y a pas d'interface NAT entre vous et votre proxy SIP, ou quand votre fournisseur de service SIP gère lui même le NAT transversal. &Use statically configured public IP address inside SIP messages &Utiliser une adresse IP publique fixe dans les messages SIP Indicates if Twinkle should use the public IP address specified in the next field inside SIP message, i.e. in SIP headers and SDP body instead of the IP address of your network interface.<br><br> When you choose this option you have to create static address mappings in your NAT device as well. You have to map the RTP ports on the public IP address to the same ports on the private IP address of your PC. Indique si Twinkle doit utiliser l'adresse IP public spécifiée dans le champ suivant dans les messages SIP, i.e. dans les entêtes SIP et dans le corps SDP à la place de l'adresse IP de votre interface réseau (privée).<br><br> En cochant cette case, vous devez créer un mappage NAT. Vous devez mapper les ports RTP sur l'adresse IP public et sur l'adresse IP privée de votre PC. Use &STUN Utiliser &STUN Choose this option when your SIP provider offers a STUN server for NAT traversal. Sélectionez cette option si votre fournisseur de service SIP propose un serveur STUN pour le NAT transversal. S&TUN server: Serveur S&TUN: The hostname, domain name or IP address of the STUN server. Le nom d l'hôte, le nom de domaine ou l'adresse IP du serveur STUN. &Public IP address: Adresse IP &publique: The public IP address of your NAT. L'adresse IP publique de votre NAT. Telephone numbers Numéros de téléphone Only &display user part of URI for telephone number &Afficher seulement la partie utilisateur de l'URI pour un numéro de téléphone If a URI indicates a telephone number, then only display the user part. E.g. if a call comes in from sip:123456@twinklephone.com then display only "123456" to the user. A URI indicates a telephone number if it contains the "user=phone" parameter or when it has a numerical user part and you ticked the next option. Si une URI indique un numéro de téléphone, alors seulement la partie utilisateur sera affichée. Ex: Si un appel arrive depuis sip:123456@twinklephone.com alors seulement "123456" sera affiché. Une URI indique un numérode téléphone si elle contient le paramêtre "user=phone" ou si elle a une partie utilisateur numérique et que vous avez coché la case suivante. &URI with numerical user part is a telephone number Une &URI avec une partie utilisateur numérique est un numéro de téléphone If you tick this option, then Twinkle considers a SIP address that has a user part that consists of digits, *, #, + and special symbols only as a telephone number. In an outgoing message, Twinkle will add the "user=phone" parameter to such a URI. Si vous cochez cette case, Twinkle considère une adresse SIP qui a une partie utilisateur constituée de chiffre,*,#,+ et des symboles spéciaux comme un numéro de téléphone. Dans un message sortant, Twinkle ajoutera le paramètre "user=phone" pour ce type d'URI. &Remove special symbols from numerical dial strings &Supprimer les symboles spéciaux pour les chaines numériques Telephone numbers are often written with special symbols like dashes and brackets to make them readable to humans. When you dial such a number the special symbols must not be dialed. To allow you to simply copy/paste such a number into Twinkle, Twinkle can remove these symbols when you hit the dial button. Les numéros de téléphone sont souvent écrit avec des caractères spéciaux pour les rendre plus facile à lire. Quand vous composez ce genre de numéro, les caractères spéciaux doivent être supprimés. Pour vous permettre de copier/coller ce type de numéro, Twinkle supprimera ces caractères à la numérotation. &Special symbols: &Symboles spéciaux: The special symbols that may be part of a telephone number for nice formatting, but must be removed when dialing. Les symboles spéciaux utilisables pour respecter le format usuel des numéros de téléphone mais qui doivent être supprimé à la numérotation. Number conversion Conversion des numéros Match expression Expression de recherche Replace Remplacer <p> Often the format of the telphone numbers you need to dial is different from the format of the telephone numbers stored in your address book, e.g. your numbers start with a +-symbol followed by a country code, but your provider expects '00' instead of the '+', or you are at the office and all your numbers need to be prefixed with a '9' to access an outside line. Here you can specify number format conversion using Perl style regular expressions and format strings. </p> <p> For each number you dial, Twinkle will try to find a match in the list of match expressions. For the first match it finds, the number will be replaced with the format string. If no match is found, the number stays unchanged. </p> <p> The number conversion rules are also applied to incoming calls, so the numbers are displayed in the format you want. </p> <h3>Example 1</h3> <p> Assume your country code is 31 and you have stored all numbers in your address book in full international number format, e.g. +318712345678. For dialling numbers in your own country you want to strip of the '+31' and replace it by a '0'. For dialling numbers abroad you just want to replace the '+' by '00'. </p> <p> The following rules will do the trick: </p> <blockquote> <tt> Match expression = \+31([0-9]*) , Replace = 0$1<br> Match expression = \+([0-9]*) , Replace = 00$1</br> </tt> </blockquote> <h3>Example 2</h3> <p> You are at work and all telephone numbers starting with a 0 should be prefixed with a 9 for an outside line. </p> <blockquote> <tt> Match expression = 0[0-9]* , Replace = 9$&<br> </tt> </blockquote> <p> Souvent le format des numéros de téléphone que vous devez composer est différent du format eregistré dans votre carnet d'adresses, ex: votre numéro commence par "+" suivi du code pays, mais votre fournisseur de service attend "00" à la plase de "+", ou vous êtes au bureau et tous les numéros sortants doivent être précédés de "0". Vous pouvez spécifier ici une conversion de numéro en expression régulière Perl ou chaine formatée. </p> <p> Pour tous les numéros que vous compsez, Twinkle essaiera de trouver une occurence dans la liste des expressions. Lors de la première concordance, le numéro sera remplacé en suivant le formatage par chaine. Si aucune occurence n'est rencontrée, le numéro reste inchangé. </p> <p> Les règes de conversion des numéro sont également appliquées aux appels entrant. Les numéros seroont donc affichés au format souhaité. </p> <h3>Exemple 1</h3> <p> En considérant que votre code pays est 33 et que vous avez enregistré tous les numéros de votre carnet d'adresses au format internationnal, ex +338712345678. Pour la numéroation des numéro de votre propre pays, vous voulez remplacer "+33" par "0". Pour les numéros à l'étranger, vous voulez remplacer "+" par "00". </p> <p> Voici les règles à créer: </p> <blockquote> <tt> Match expression = \+31([0-9]*) , Replace =(sp)(sp)0$1<br> Match expression = \+([0-9]*) , Replace = 00$1</br> </tt> </blockquote> <h3>Exemple 2</h3> <p> Au bureau, tous les numéros commençant par 0 doivent recevoir un préfixe 9 pour un appel sortant. </p> <blockquote> <tt> Match expression = 0[0-9]* , Replace =(sp)(sp)9$&<br> </tt> </blockquote> Move the selected number conversion rule upwards in the list. Déplacer la règle de conversion sélectionnée vers le haut de la liste. Move the selected number conversion rule downwards in the list. Déplacer la règle de conversion sélectionnée vers le bas de la liste.. &Add &Ajouter Add a number conversion rule. Ajouter une règle de conversion de numéro. Re&move &Supprimer Remove the selected number conversion rule. Supprimer la règle de conversion de numéro sélectionnée. &Edit &Editer Edit the selected number conversion rule. Editer la règle de conversion de numéro sélectionnée.. Type a telephone number here an press the Test button to see how it is converted by the list of number conversion rules. Saisissez ici un numéro de téléphone et appuyer sur le bouton Test pour voir comment il est converti par la liste des règles de conversion de numéros. &Test &Test Test how a number is converted by the number conversion rules. Test la façon de convertir d'un numéro par les règles de conversion. for STUN pour STUN Keep alive timer for the STUN protocol. If you have enabled STUN, then Twinkle will send keep alive packets at this interval rate to keep the address bindings in your NAT device alive. Garder actif le décompte pour le protocole STUN. Si vous avez activé STUN, Twinkle enverra des paquets à cet intervalle pour garder l'adresse dans votre interface NAT active. When an incoming call is received, this timer is started. If the user answers the call, the timer is stopped. If the timer expires before the user answers the call, then Twinkle will reject the call with a "480 User Not Responding". Quand un appel entrant est reçu, le décompte est lancé. Si l'utilisateur répond à l'appel, le chronomètre est arrèté. Si le décompte se termine avant que l'utilisateur ne réponde, Twinkle rejettera l'appel avec un message "480 User Not Responding" (L'utilisateur ne répond pas). NAT &keep alive: &STUN NAT-keep-alive: &No answer: &Pas de réponse: Ring &back tone: &Tonalité: <p> Specify the file name of a .wav file that you want to be played as ring back tone for this user. </p> <p> This ring back tone overrides the ring back tone settings in the system settings. </p> <p>Entrez un nom de fichier wav qui sera utilisé comme tonalité pour cette utilisateur.</p> <p>Cette tonalité est prioritaire sur celle définie dans les paramètres système.</p> <p> Specify the file name of a .wav file that you want to be played as ring tone for this user. </p> <p> This ring tone overrides the ring tone settings in the system settings. </p> <p>Entrez un nom de fichier wav qui sera utilisé comme sonnerie pour cette utilisateur.</p> <p>Cette sonnerie est prioritaire sur celle définie dans les paramètres système.</p> &Ring tone: &Sonnerie: <p> This script is called when you release a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing SIP BYE request are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=local_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the BYE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Ce script est appelé au raccrochage. </p> <h3>Variables d'environnement</h3> <p> Les valeurs de toutes les entêtes SIP de la demande sortante SIP BYE sont passées en variables d'environnement à votre script. </p> <p> <b>TWINKLE_TRIGGER=local_release</b>. <br> <b>SIPREQUEST_METHOD=BYE</b>. <br> <b>SIPREQUEST_URI</b> contient la demande URI de BYE. <br> <b>TWINKLE_USER_PROFILE</b> contient le nom du profil utilisé. <p> This script is called when an incoming call fails. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing SIP failure response are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=in_call_failed</b>. <b>SIPSTATUS_CODE</b> contains the status code of the failure response. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Ce script est appelé en cas d'échec d'appel entrant. </p> <h3>Variables d'environnement</h3> <p> Les valeurs de toutes les entêtes SIP de la réponse d'échec sortante SIP sont passées en variables d'environnement à votre script. </p> <p> <b>TWINKLE_TRIGGER=in_call_failed</b>. <br> <b>SIPSTATUS_CODE</b> contient le code de statut de la réponse d'échec.<br> <b>SIPSTATUS_REASON</b> contient la raison.<br> <b>TWINKLE_USER_PROFILE</b> contient le nom du profil utilisé. <p> This script is called when the remote party releases a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming SIP BYE request are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=remote_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the BYE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Ce script est appelé en cas de raccrochage de la part du correspondant. </p> <h3>Variables d'environnement</h3> <p> Les valeurs de toutes les entêtes SIP de la demande entrante SIP BYE sont passées en variables d'environnement à votre script. </p> <p> <b>TWINKLE_TRIGGER=remote_release</b>. <br> <b>SIPREQUEST_METHOD=BYE</b>. <br> <b>SIPREQUEST_URI</b> contient la demande URI de BYE. <br> <b>TWINKLE_USER_PROFILE</b> contient le nom du profil utilisé. <p> This script is called when the remote party answers your call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming 200 OK are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Ce script est appelé quand votre correspondant répond à votre appel. </p> <h3>Variables d'environnement</h3> <p> Les valeurs de toutes les entêtes SIP d'un 200 OK entrant sont passées en variables d'environnement à votre script. </p> <p> <b>TWINKLE_TRIGGER=out_call_answered</b>. <br> <b>SIPSTATUS_CODE=200</b><br> <b>SIPSTATUS_REASON</b> contient la raison.<br> <b>TWINKLE_USER_PROFILE</b> contient le nom du profil utilisé. <p> This script is called when you answer an incoming call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing 200 OK are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=in_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Ce script est appelé quand vous répondez à un appel entrant. </p> <h3>Variables d'environnement</h3> <p> Les valeurs de toutes les entêtes SIP d'un 200 OK sortant sont passées en variables d'environnement à votre script. </p> <p> <b>TWINKLE_TRIGGER=in_call_answered</b>. <br> <b>SIPSTATUS_CODE=200</b><br> <b>SIPSTATUS_REASON</b> contient la raison.<br> <b>TWINKLE_USER_PROFILE</b> contient le nom du profil utilisé. Call released locall&y: Appel raccroché locale&ment: <p> This script is called when an outgoing call fails. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming SIP failure response are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call_failed</b>. <b>SIPSTATUS_CODE</b> contains the status code of the failure response. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Ce script est appelé en cas d'échec d'appel sortant. </p> <h3>Variables d'environnement</h3> <p> Les valeurs de toutes les entêtes SIP de la réponse d'échec entrante SIP sont passées en variables d'environnement à votre script. </p> <p> <b>TWINKLE_TRIGGER=out_call_failed</b>. <br> <b>SIPSTATUS_CODE</b> contient le code de statut de la réponse d'échec.<br> <b>SIPSTATUS_REASON</b> contient la raison.<br> <b>TWINKLE_USER_PROFILE</b> contient le nom du profil utilisé. <p> This script is called when you make a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing INVITE are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call</b>. <b>SIPREQUEST_METHOD=INVITE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the INVITE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Ce script est appelé quand vous passez un appel. </p> <h3>Variables d'environnement</h3> <p> Les valeurs de toutes les entêtes SIP d'un INVITE sortant sont passées en variables d'environnement à votre script. </p> <p> <b>TWINKLE_TRIGGER=out_call</b>. <br> <b>SIPSTATUS_CODE=INVITE</b><br> <b>SIPREQUEST_URI</b> contient la demande URI de INVITE. <br> <b>TWINKLE_USER_PROFILE</b> contient le nom du profil utilisé. Outgoing call a&nswered: Appel sortant répo&ndu: Incoming call &failed: Appel entrant &manqué: &Incoming call: Appel &entrant: Call released &remotely: Appel &terminé par le correspondant: Incoming call &answered: Appel entrant répo&ndu: O&utgoing call: Appel &sortant: Out&going call failed: Appel entrant &échoué: &Enable ZRTP/SRTP encryption Activer la cryptographie &ZRTP/SRTP When ZRTP/SRTP is enabled, then Twinkle will try to encrypt the audio of each call you originate or receive. Encryption will only succeed if the remote party has ZRTP/SRTP support enabled. If the remote party does not support ZRTP/SRTP, then the audio channel will stay unecrypted. Quand ZRT/SRTP est activé, Twinkle essaiera de crypter les appels que vous émettez ou recevez. L'encodage ne réussira que si le correspondant accepte le ZRTP/SRTP. Dans le cas contraire l'appel ne sera pas crypté. ZRTP settings Paramètres ZRTP O&nly encrypt audio if remote party indicated ZRTP support in SDP Encoder &seulement si le correspondant accepte le ZRTP en SDP A SIP endpoint supporting ZRTP may indicate ZRTP support during call setup in its signalling. Enabling this option will cause Twinkle only to encrypt calls when the remote party indicates ZRTP support. Un terminal SIP supportant ZRTP doit l'indiquer pendant l'établissement de l'appel. En cochant cette case, Twinkle n'encryptera les appels que si le correspondant indique qu'il accepte le ZRTP. &Indicate ZRTP support in SDP &Indiquer le support de ZRTP dans SDP Twinkle will indicate ZRTP support during call setup in its signalling. Twinkle indiquera qu'il accepte le ZRTP dans sa signature pendant l'établissement de l'appel. &Popup warning when remote party disables encryption during call Afficher un &avertissement quand le correspondant désactive la cryptographie pendant l'appel A remote party of an encrypted call may send a ZRTP go-clear command to stop encryption. When Twinkle receives this command it will popup a warning if this option is enabled. Un correspondant d'un appel encrypté doit envoyer une commande de fin ZRTP (go-clear) pour arréter la cryptographie. Quand Twinkle reçoit cette commande, un avertissement s'affichera si cette case est cochée. Dynamic payload type %1 is used more than once. Le type %1 de charge utile dynamique est utilisé plu d'une fois. You must fill in a user name for your SIP account. Vous devez saisir un nom d'utilisateur pour votre compte SIP. You must fill in a domain name for your SIP account. This could be the hostname or IP address of your PC if you want direct PC to PC dialing. Vous devez saisir un nom de domaine pour votre compte SIP. il est également possible de saisir le nom de l'hôte ou l'adresse IP de votre PC si vous voulez faire des appels directs de PC à PC. Invalid user name. Nom d'utilisateur invalide. Invalid domain. Domaine invalide. Invalid value for registrar. Registrar invalde. Invalid value for outbound proxy. Proxy sortant invalide. Value for public IP address missing. Absence d'adresse IP publique. Invalid value for STUN server. Absence de valeur pour le serveur STUN. Ring tones Description of .wav files in file dialog Sonneries Choose ring tone Choisir une sonnerie Ring back tones Description of .wav files in file dialog Sonneries de tonalité All files Tous les fichiers Choose incoming call script Sélectionner le scipt d'appel entrant Choose incoming call answered script Sélectionner le scipt d'appel entrant répondu Choose incoming call failed script Sélectionner le scipt d'appel entrant échoué Choose outgoing call script Sélectionner le scipt d'appel sortant Choose outgoing call answered script Sélectionner le scipt d'appel sortant répondu Choose outgoing call failed script Sélectionner le scipt d'appel sortant échoué Choose local release script Sélectionner le scipt de raccrochage local Choose remote release script Sélectionner le scipt de raccrochage local distant Voice mail Boîte vocale &Follow codec preference from far end on incoming calls &Suivre la préférence de codec du correspondant pour les appels entrants <p> For incoming calls, follow the preference from the far-end (SDP offer). Pick the first codec from the SDP offer that is also in the list of active codecs. <p> If you disable this option, then the first codec from the active codecs that is also in the SDP offer is picked. Choisit le premier codec de la demande SDP qui est également dans la liste des codecs actifs.<br> Si vous désactivez cette option, c'est le premier codec de la liste des codecs actifs qui est également dans la demande SDP qui est choisi. Follow codec &preference from far end on outgoing calls &Suivre la préférence de codec du correspondant pour les appels sortants <p> For outgoing calls, follow the preference from the far-end (SDP answer). Pick the first codec from the SDP answer that is also in the list of active codecs. <p> If you disable this option, then the first codec from the active codecs that is also in the SDP answer is picked. Choisit le premier codec de la réponse SDP qui est également dans la liste des codecs actifs.<br> Si vous désactivez cette option, c'est le premier codec de la liste des codecs actifs qui est également dans la réponse SDP qui est choisi. Codeword &packing order: Ordre de compression du codec (codeword &packing order): RFC 3551 ATM AAL2 There are 2 standards to pack the G.726 codewords into an RTP packet. RFC 3551 is the default packing method. Some SIP devices use ATM AAL2 however. If you experience bad quality using G.726 with RFC 3551 packing, then try ATM AAL2 packing. Il existe 2 standards de compression pour le codec G726 dans un paquet RTP. RFC 3551 est la compression par défaut. Quelques interfaces SIP utilisent ATM AAL2. Si vous avez une mauvaise qualité en G726 avec la compression RFC 3551, utilisez ATM AAL2. Replaces Remplace Indicates if the Replaces-extenstion is supported. Indique si Replaces-Extension est supporté. Attended refer to AoR (Address of Record) Refer avec consultation vers "Address of Record" An attended call transfer should use the contact URI as a refer target. A contact URI may not be globally routable however. Alternatively the AoR (Address of Record) may be used. A disadvantage is that the AoR may route to multiple endpoints in case of forking whereas the contact URI routes to a single endoint. Un transfert avec consultation doit utiliser l'URI du contact comme cible du refer. Cependant, une URI de contact ne doit pas être routable. L'AoR doit être utlisée à la place. Un incovéniant est que l'AoR peut router vers plusieurs destinataires alors que l'URI de contact route vers un destinataire unique. Privacy Confidentialité Privacy options Options de confidentialité &Send P-Preferred-Identity header when hiding user identity &Envoyer "P-Preferred-Identity Header" quand l'identité est cachée Include a P-Preferred-Identity header with your identity in an INVITE request for a call with identity hiding. Inclure un "P-Preferred-Identity Header" à votre identité dans une demande INVITE pour un appel à l'identité cachée. <p> You can customize the way Twinkle handles incoming calls. Twinkle can call a script when a call comes in. Based on the output of the script Twinkle accepts, rejects or redirects the call. When accepting the call, the ring tone can be customized by the script as well. The script can be any executable program. </p> <p> <b>Note:</b> Twinkle pauses while your script runs. It is recommended that your script does not take more than 200 ms. When you need more time, you can send the parameters followed by <b>end</b> and keep on running. Twinkle will continue when it receives the <b>end</b> parameter. </p> <p> With your script you can customize call handling by outputting one or more of the following parameters to stdout. Each parameter should be on a separate line. </p> <p> <blockquote> <tt> action=[ continue | reject | dnd | redirect | autoanswer ]<br> reason=&lt;string&gt;<br> contact=&lt;address to redirect to&gt;<br> caller_name=&lt;name of caller to display&gt;<br> ringtone=&lt;file name of .wav file&gt;<br> display_msg=&lt;message to show on display&gt;<br> end<br> </tt> </blockquote> </p> <h2>Parameters</h2> <h3>action</h3> <p> <b>continue</b> - continue call handling as usual<br> <b>reject</b> - reject call<br> <b>dnd</b> - deny call with do not disturb indication<br> <b>redirect</b> - redirect call to address specified by <b>contact</b><br> <b>autoanswer</b> - automatically answer a call<br> </p> <p> When the script does not write an action to stdout, then the default action is continue. </p> <p> <b>reason: </b> With the reason parameter you can set the reason string for reject or dnd. This might be shown to the far-end user. </p> <p> <b>caller_name: </b> This parameter will override the display name of the caller. </p> <p> <b>ringtone: </b> The ringtone parameter specifies the .wav file that will be played as ring tone when action is continue. </p> <h2>Environment variables</h2> <p> The values of all SIP headers in the incoming INVITE message are passed in environment variables to your script. The variable names are formatted as <b>SIP_&lt;HEADER_NAME&gt;</b> E.g. SIP_FROM contains the value of the from header. </p> <p> TWINKLE_TRIGGER=in_call. SIPREQUEST_METHOD=INVITE. The request-URI of the INVITE will be passed in <b>SIPREQUEST_URI</b>. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Vous pouvez personnaliser la façon dont Twinkle gère les appels entrants. Twinkle peut appel un script quand un appel arriver. Sur la base de la sortie du script, Twinkle accepte, rejette ou redirige l'appel. Si l'appel est accepté, la sonnerie peut également être presonnalisée par un script. Le script peut être un programme executable. </p> <p> <b>Nota:</b> Twinkle est suspendu pendant que votre script tourne. Il est recommandé que votre script ne dure pas plus de 200 ms. Quand vous avez besoin de plus de temps, vous pouvez envoyer les paramètres suivis de <b>end</b> et continuer d'executer. Twinkle continuera quand il recevra le paramètre <b>end</b>.(new line) </p> <p> Avec votre script vous pouvez personnaliser la gestion d'appel en renvoyant un ou plusieurs des paramètres suivants à stdout. Chaque paramètre doit être sur un ligne différente. </p> <p> <blockquote> <tt> action=[ continue | reject | dnd | redirect | autoanswer ]<br> reason=&lt;string&gt;<br> contact=&lt;adresse de redirection&gt;<br> caller_name=&lt;non de l'appelant à afficher&gt;<br> ringtone=&lt;nom de fichier wav&gt;<br> display_msg=&lt;message à afficher sur l'écran&gt;<br> end<br> </tt> </blockquote> </p> <h2>Paramètres</h2> <h3>action</h3> <p> <b>continue</b> - poursuivre la gestion d'appel comme d'habitude<br> <b>reject</b> - rejeter l'appel<br> <b>dnd</b> - rejeter l'appel avec un message "ne pas derranger"<br> <b>redirect</b> - rediriger l'appel vers l'adresse de<b>contact</b><br> <b>autoanswer</b> - répondre automatiquement à l'appel<br> </p> <p> Quand le script ne renvoie pas d'action à stdout, alors l'action par défaut se poursuit. </p> <p> <b>reason: </b> Avec le paramètre "reason" vous pouvez affecter la valeur "reject" ou "dnd". Ce sera montré au correspondant. </p> <p> <b>caller_name: </b> Ce paramètre ne tient pas compte du nom l'appelant affiché. </p> <p> <b>ringtone: </b> Ce paramètre indique quel fichier wav utiliser comme sonnerie. </p> <h2>Environment variables</h2> <p> Les valeurs de toutes les entêtes SIP d'un message INVITE entrant sont passées en variable d'environnement à votre script. Les nom de varibles sont les suivants:<b>SIP_&lt;HEADER_NAME&gt;</b> E.g. SIP_FROM contien le valeur de l'entête from. </p> <p> TWINKLE_TRIGGER=in_call. SIPREQUEST_METHOD=INVITE. la demande URI d'une INVITE sera dans <b>SIPREQUEST_URI</b>. le nom du profile utilisateur sera dans: <b>TWINKLE_USER_PROFILE</b>. &Voice mail address: Adresse de la &boîte vocale: The SIP address or telephone number to access your voice mail. L'adresse SIP ou le numéro de téléphone pour accéder à votre boît vocale. Unsollicited Non désiré Sollicited RFC 3842 <H2>Message waiting indication type</H2> <p> If your provider offers the message waiting indication service, then Twinkle can show you when new voice mail messages are waiting. Ask your provider which type of message waiting indication is offered. </p> <H3>Unsollicited</H3> <p> Asterisk provides unsollicited message waiting indication. </p> <H3>Sollicited</H3> <p> Sollicited message waiting indication as specified by RFC 3842. </p> <H2>Type de message en attente</H2>(new line) <p>(new line) Si votre fournisseur de service propose la signalisation d'un message d'attente , Twinkle peut vous indiquer qu'un nouveau message est arrivé. Demander à votre fournisseur, quel type de signalisation est fourni.(new line) </p>(new line) <H3>Non sollicité</H3>(new line) <p>(new line) Signalisation de message en attente non sollicité .(new line) </p>(new line) <H3>Sollicité</H3>(new line) <p>(new line) Signalisation de message en attente sollicité com spécifié par le RFC 3842.(new line) </p> &MWI type: Type &MWI: Sollicited MWI RFC 3842 Subscription &duration: &durée de souscription: Mailbox &user name: Nom d'&utilisateur de boîte vocale: The hostname, domain name or IP address of your voice mailbox server. Le nom d l'hôte, le nom de domaine ou l'adresse IP du serveur de boîte vocale. For sollicited MWI, an endpoint subscribes to the message status for a limited duration. Just before the duration expires, the endpoint should refresh the subscription. Pour le RFC 3842 MWI sollicité, un terminal souscrit au message de statut pour une durée limité. Juste avant l'expiration le terminal doit raffraichir la subcription. Your user name for accessing your voice mailbox. Votre nom d'utilisateur de boîte vocale. Mailbox &server: &Server de boîte vocale: Via outbound &proxy Via &proxy sortant Check this option if Twinkle should send SIP messages to the mailbox server via the outbound proxy. Cocher cette case si Twinkle doit envoyer les messages au serveur de boîte vocale par un proxy sortant. You must fill in a mailbox user name. Vous devez saisir un nom d'utilisateur de boîte vocale. You must fill in a mailbox server Vous devez saisir un nom de serveur de boîte vocale Invalid mailbox server. Serveur de boîte vocale invalide. Invalid mailbox user name. Nom d'utilisateur de boîte vocale invalide. Use domain &name to create a unique contact header value Utiliser le &nom de domaine pour créer une valeur unique de l'entête de contact Select ring back tone file. Sélectionnez votre sonnerie de tonalité. Select ring tone file. Sélectionner un fichier de sonnerie. Select script file. Sélectionner un script. %1 converts to %2 %1 convertit en %2 Instant message Message instantané Presence Présence &Maximum number of sessions: Nombre &maximum de sessions: When you have this number of instant message sessions open, new incoming message sessions will be rejected. Quand ce nombre de sessions de message instantané est atteint, les nouvelles sessions de message entrant seront rejetées. Your presence Votre présence &Publish availability at startup &Publier la disponibilité au démarrage Publish your availability at startup. Publier la disponibilité au démarrage. Buddy presence Présence d'avatar Publication &refresh interval (sec): Intervalle d'&actualisation de la publication (sec): Refresh rate of presence publications. Période d'actualisation de la publication de votre présence. &Subscription refresh interval (sec): Intervalle d'&actualisation de la connexion (sec): Refresh rate of presence subscriptions. Période d'actualisation de la connexion. Transport/NAT Transport/NAT Add q-value to registration Ajouter la q-value à la connexion The q-value indicates the priority of your registered device. If besides Twinkle you register other SIP devices for this account, then the network may use these values to determine which device to try first when delivering a call. La q-value indique la priorité de l'interface connectée. Si en plus de Twinkle, vous connectez une autre interface SIP pour ce compte, alors le réseau utilisera ces valeurs pour déterminer quelle interface tester en premier pour effectuer un appel. The q-value is a value between 0.000 and 1.000. A higher value means a higher priority. La q-value est une valeur comprise ent 0.000 et 1.000. Une valeur superieure signifie une priorité plus importante. SIP transport Transport SIP UDP TCP Transport mode for SIP. In auto mode, the size of a message determines which transport protocol is used. Messages larger than the UDP threshold are sent via TCP. Smaller messages are sent via UDP. Mode de transport SIP. En mode auto, la taille du message détermine quel protocole de transport utiliser. Les messages plus grand que la limite de l'UDP sont envoyés via TCP. T&ransport protocol: Protocole de T&ransport: UDP t&hreshold: &Limite UDP: bytes octets Messages larger than the threshold are sent via TCP. Smaller messages are sent via UDP. Les messages plus grand que la limite sont envoyés via TCP. Les messages plus petits sont envoyés via UDP. Use &STUN (does not work for incoming TCP) Utiliser &STUN (ne fonctionne pas pour le TCP entrant) P&ersistent TCP connection Connexion TCP p&ercistante Keep the TCP connection established during registration open such that the SIP proxy can reuse this connection to send incoming requests. Application ping packets are sent to test if the connection is still alive. Conserve la connexion TCP pendant l'ouverture de l'enregistrement de façon à ce que le proxy SIP puisse réutiliser cette connexion pour envoyer des requêtes. Des ping sont enoyés pour tester si la connexion est toujours en établie. &Send composing indications when typing a message. &Envoi de l'indication composite en un message. Twinkle sends a composing indication when you type a message. This way the recipient can see that you are typing. Twinkle envoie une indication composite quand vous écrivez un message. Ainsi, l'interlocuteur peut voir que vous êtes en train d'écrire un message. AKA AM&F: AKA AM&F: A&KA OP: A&KA OP: Authentication management field for AKAv1-MD5 authentication. Champ de gestion de l'authentification en AKAv1-MD5. Operator variant key for AKAv1-MD5 authentication. Clé de l'opérateur en AKAv1-MD5. Prepr&ocessing Préinterprétati&on Preprocessing (improves quality at remote end) La préinterpretation améliore la qualité distante &Automatic gain control Contrôle &automatique du gain Automatic gain control (AGC) is a feature that deals with the fact that the recording volume may vary by a large amount between different setups. The AGC provides a way to adjust a signal to a reference volume. This is useful because it removes the need for manual adjustment of the microphone gain. A secondary advantage is that by setting the microphone gain to a conservative (low) level, it is easier to avoid clipping. Le contrôle automatique du gain (AGC) tient compte de la possible variation importante du volume d'enregistrement entre deux paramètres. L'AGC permet d'ajuste le volume de référence. Ceci est très pratique car il supprime la nécessité d'ajuster le gain du microphone manuellement. Un autre avantage est de permettre de régler le gain du microphone à un niveau assez bas pour éviter les coupures. Automatic gain control &level: &Niveau du contrôle automtaique du gain: Automatic gain control level represents percentual value of automatic gain setting of a microphone. Recommended value is about 25%. Ce niveau représente un pourcentage du gain du microphone. La valeur recommandée est 25%. &Voice activity detection Détection de l'activité de la &voix When enabled, voice activity detection detects whether the input signal represents a speech or a silence/background noise. La détection de l'activité détecte si le signal d'entrée est de la parole ou du silence/bruit de fond et ne transmet pas ce bruit. &Noise reduction Réduction du &bruit The noise reduction can be used to reduce the amount of background noise present in the input signal. This provides higher quality speech. La réduction du bruit peut être utilisée pour réduire le niveau de bruit de fond dans le signal d'entrée. Ceci améliore la qualité du son. Acoustic &Echo Cancellation Suppression de l'&Echo In any VoIP communication, if a speech from the remote end is played in the local loudspeaker, then it propagates in the room and is captured by the microphone. If the audio captured from the microphone is sent directly to the remote end, then the remote user hears an echo of his voice. An acoustic echo cancellation is designed to remove the acoustic echo before it is sent to the remote end. It is important to understand that the echo canceller is meant to improve the quality on the remote end. Dans toute communication VOIP, si le son venant du correspondant est sur amplificateur, il est enregistré par le microphone. Si le son enregistré par le micropohne est directement envoyé au correspondant, alors il entend sa voix en écho. La suppression de l'écho est consue pour supprimer cet écho avant qu'il ne soit transmis. Il est important de comprendre que la suppression de l'écho est consue pour améliorer la qualite du son pour le correspondant. Variable &bit-rate Taux de compression varia&ble Discontinuous &Transmission &Transmission discontinue &Quality: &Qualité: Speex is a lossy codec, which means that it achives compression at the expense of fidelity of the input speech signal. Unlike some other speech codecs, it is possible to control the tradeoff made between quality and bit-rate. The Speex encoding process is controlled most of the time by a quality parameter that ranges from 0 to 10. Speex est un codec à perte, ce qui signifie qu'il assure la compression au dépend de la fidélité au son d'entrée. A la différence de quelques autres codecs, il est possible de contrôler l'équilibre entre qualité et compression. Le pocessus d'encodage Speex est contrôlé la plupart du temps par un paramêtre de qualité entre 0 et 10. bytes octets Use tel-URI for telephone &number Utiliser tel-URI comme &numéro de téléphone Expand a dialed telephone number to a tel-URI instead of a sip-URI. Transcrit un numéro de téléphone entré en tel-URI et non en sip-URI. Accept call &transfer request (incoming REFER) Allow call transfer while consultation in progress When you perform an attended call transfer, you normally transfer the call after you established a consultation call. If you enable this option you can transfer the call while the consultation call is still in progress. This is a non-standard implementation and may not work with all SIP devices. Enable NAT &keep alive Send UDP NAT keep alive packets. If you have enabled STUN or NAT keep alive, then Twinkle will send keep alive packets at this interval rate to keep the address bindings in your NAT device alive. WizardForm Twinkle - Wizard Twinkle - Assitant The hostname, domain name or IP address of the STUN server. Le nom d l'hôte, le nom de domaine ou l'adresse IP du serveur STUN. S&TUN server: Serveur S&TUN: The SIP user name given to you by your provider. It is the user part in your SIP address, <b>username</b>@domain.com This could be a telephone number. <br><br> This field is mandatory. Le nom d'utilisateur SIP donné par votre fournisseur de service. C'est la partie "utilisateur" de votre adresse SIP, <b>utilisateur</b>@domain.com. Ceci peut être un numéro de téléphone. <br><br> Ce champ est obligatoire. &Domain*: Choose your SIP service provider. If your SIP service provider is not in the list, then select <b>Other</b> and fill in the settings you received from your provider.<br><br> If you select one of the predefined SIP service providers then you only have to fill in your name, user name, authentication name and password. Sélectionnez votre fournisseur de service SIP. S'il n'es pas dans la liste, sélectionnez <b>Autre</b> et remplissez les paramètres que vous avez reçu de votre fournisseur.<br><br> Si vous sélectionnez un des fournisseurs prédéfinis, vous n'avez qu'à saisir votre nom, nom d'utilisateur, nom d'authentification, et mot de passe. &Authentication name: Nom d'&authentification: &Your name: &Votre nom: Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Votre nom d'authentification SIP. Souvent il est le même que votre nom d'utilisateur SIP. Cependant, il peut être différent. The domain part of your SIP address, username@<b>domain.com</b>. Instead of a real domain this could also be the hostname or IP address of your <b>SIP proxy</b>. If you want direct IP phone to IP phone communications then you fill in the hostname or IP address of your computer. <br><br> This field is mandatory. Le domaine de votre adresse SIP, username@<b>domain.com</b>. A la place d'un vrai nom de domaine, il est également possible de saisir le nom de l'hôte ou l'adresse IP de votre <b>proxy SIP</b>. Si vous voulez uniquement des communications de PC à PC, saisissez le nom de l'hôte ou l'adresse IP de votre ordinateur. <br><br> Ce champ est obligatoire. This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. C'est simplement votre nom complet, ex: Pierre Dupond. Il est utilisé pour l'affichage. Quand vous ferez un appel, ceci sera montré à votre correspondant. SIP pro&xy: Pro&xy SIP: The hostname, domain name or IP address of your SIP proxy. If this is the same value as your domain, you may leave this field empty. Le nom d'hôte, le nom de domaine ou l'adresse IP de votre proxy SIP. Si c'est le même valeur que votre domaine, vous pouvez laisser ce champ vide. &SIP service provider: Fournisseur de service &SIP: &Password: Mot de &passe: &User name*: Nom d'&utilisateur*: Your password for authentication. Votre mot de passe pour authentification. &OK Alt+O &Cancel Annuler (Es&c) Alt+C None (direct IP to IP calls) Aucun (appels directs d'IP à IP) Other Autre User profile wizard: Assistant pour profil utilisateur: You must fill in a user name for your SIP account. Vous devez saisir un nom d'utilisateur pour votre compte SIP. You must fill in a domain name for your SIP account. This could be the hostname or IP address of your PC if you want direct PC to PC dialing. Vous devez saisir un nom de domaine pour votre compte SIP. il est également possible de saisir le nom de l'hôte ou l'adresse IP de votre PC si vous voulez faire des appels directs de PC à PC. Invalid value for SIP proxy. Valeur invalide pour le proxy SIP. Invalid value for STUN server. Valeur invalide pour le serveur STUN. YesNoDialog &Yes &Oui &No &Non incoming_call Answer Répondre Reject Refuser twinkle-1.10.1/src/gui/lang/twinkle_nl.ts000066400000000000000000010222671277565361200203110ustar00rootroot00000000000000 AddressCardForm Twinkle - Address Card Twinkle - Adres &Remark: Op&merkingen: Infix name of contact. Tussenvoegsel. First name of contact. Voornaam. &First name: Voor&naam: You may place any remark about the contact here. Hier kunt u opmerkingen kwijt. &Phone: &Telefoon: &Infix name: Tu&ssenvoegsel: Phone number or SIP address of contact. Telefoonnummer of SIP adres. Last name of contact. Achternaam. &Last name: &Achternaam: &OK &OK Alt+O &Cancel Ann&uleren Alt+C Alt+U You must fill in a name. U moet een naam invullen. You must fill in a phone number or SIP address. U moet een telefoonnummer of SIP adres invullen. AddressTableModel Name Naam Phone Telefoon Remark Opmerkingen AuthenticationForm Twinkle - Authentication Twinkle - Authenticatie The user for which authentication is requested. De gebruiker waarvoor authenticatie vereist is. The user profile of the user for which authentication is requested. Het gebruikersprofiel van de gebruiker. User profile: Gebruikersprofiel: User: Gebruiker: &Password: &Paswoord: Your password for authentication. Uw paswoord voor authenticatie. Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Uw SIP gebruikersnaam voor authenticatie. Meestal is dit hetzelfde als uw SIP gebruikersnaam. &User name: &Gebruikersnaam: &OK &OK &Cancel Ann&uleren Login required for realm: Realm: The realm for which you need to authenticate. De "realm" waarvoor u zich moet authenticeren. user No need to translate profile No need to translate realm No need to translate BuddyForm Twinkle - Buddy Twinkle - Vriend Address book Adresboek Select an address from the address book. Kies een adres uit het adresboek. &Phone: &Telefoon: Name of your buddy. Naam van uw vriend. &Show availability &Toon beschikbaarheid Alt+S Alt+T Check this option if you want to see the availability of your buddy. This will only work if your provider offers a presence agent. Vink deze optie aan als u de beschikbaarheid van uw vriend wilt zien. Dit werkt alleen als uw provider een presence agent heeft. &Name: &Naam: SIP address your buddy. SIP adres van uw vriend. &OK &OK Alt+O &Cancel Ann&uleren Alt+C Alt+U You must fill in a name. U moet een naam invullen. Invalid phone. Foutief telefoonnummer. Failed to save buddy list: %1 Opslaan van vriendenlijst is mislukt: %1 BuddyList Availability Beschikbaarheid unknown onbekend offline offline online online request rejected verzoek geweigerd not published niet gepubliceerd failed to publish publicatie mislukt request failed verzoel mislukt Click right to add a buddy. Klik rechts om een vriend toe te voegen. CoreAudio Failed to open sound card Openen geluidskaart mislukt Failed to create a UDP socket (RTP) on port %1 UDP socket (RTP) creatie op port %1 mislukt Failed to create audio receiver thread. Creatie van audio receiver thread mislukt. Failed to create audio transmitter thread. Creatie van audio transmitter thread mislukt. CoreCallHistory local user lokale partij remote user andere partij failure fout unknown onbekend in in out uit DeregisterForm Twinkle - Deregister Twinkle - Deregistreren deregister all devices deregistreer alle apparaten &OK &OK &Cancel Ann&uleren DiamondcardProfileForm Twinkle - Diamondcard User Profile Twinkle - Diamondcard gebruikersprofiel <p>With a Diamondcard account you can make worldwide calls to regular and cell phones. To sign up for a Diamondcard account click on the "sign up" link below. Once you have signed up you receive an account ID and PIN code. Enter the account ID and PIN code below to create a Twinkle user profile for your Diamondcard account.</p> <p>For call rates see the sign up web page that will be shown to you when you click on the "sign up" link.</p> <p>Met een Diamondcard account kunt u wereldwijd bellen naar vaste en mobiele telefoons. U kunt zich aanmelden voor een Diamondcard account door op de onderstaande "aanmelden" link te klikken. Na aanmelding ontvangt u een account ID en PIN code. Voer dit account ID en de PIN code hieronder in om een Twinkle gebruikersprofiel voor uw Diamondcard account te maken.</p> <p>Beltarieven kunt u vinden op de aanmeldingspagina die u krijgt als u op de "aanmelden" link klinkt.</p> Your Diamondcard account ID. Uw Diamondcard account ID. This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. Dit is uw eigen naam. bijv. Jan Jansen. Als u iemand belt, kan deze naam getoond worden. &Account ID: &Account ID: &PIN code: &PIN code: &Your name: U&w naam: <p align="center"><u>Sign up for a Diamondcard account</u></p> <p align="center"><u>Aanmelden voor een Diamondcard account</u></p> &OK &OK &Cancel Ann&uleren Fill in your account ID. Vul uw account ID in. Fill in your PIN code. Vul uw PIN code in. A user profile with name %1 already exists. Een gebruikersprofiel met de naam %1 bestaat al. Your Diamondcard PIN code. Uw Diamondcard PIN code. <p>With a Diamondcard account you can make worldwide calls to regular and cell phones and send SMS messages. To sign up for a Diamondcard account click on the "sign up" link below. Once you have signed up you receive an account ID and PIN code. Enter the account ID and PIN code below to create a Twinkle user profile for your Diamondcard account.</p> <p>For call rates see the sign up web page that will be shown to you when you click on the "sign up" link.</p> <p>Met een Diamondcard account kunt u wereldwijd bellen naar vaste en mobiele telefoons en SMS berichten versturen. U kunt zich aanmelden voor een Diamondcard account door op de onderstaande "aanmelden" link te klikken. Na aanmelding ontvangt u een account ID en PIN code. Voer dit account ID en de PIN code hieronder in om een Twinkle gebruikersprofiel voor uw Diamondcard account te maken.</p> <p>Beltarieven kunt u vinden op de aanmeldingspagina die u krijgt als u op de "aanmelden" link klinkt.</p> DtmfForm Twinkle - DTMF Twinkle - DTMF Keypad Toetsen 2 2 3 3 Over decadic A. Normally not needed. A (normaal niet nodig). 4 4 5 5 6 6 Over decadic B. Normally not needed. B (normaal niet nodig). 7 7 8 8 9 9 Over decadic C. Normally not needed. C (normaal niet nodig). Star (*) Ster (*) 0 0 Pound (#) Hekje (#) Over decadic D. Normally not needed. D (normaal niet nodig). 1 1 &Close &Sluiten Alt+C Alt+S FreeDeskSysTray Show/Hide Toon/Verberg Quit Afsluiten GUI Cannot find a network interface. Twinkle will use 127.0.0.1 as the local IP address. When you connect to the network you have to restart Twinkle to use the correct IP address. Geen netwerkverbinding gevonden. Twinkle gebruikt nu 127.0.0.1 als lokaal IP adres. Als u weer een netwerkverbinging heeft, dan moet u Twinkle opnieuw starten om het correcte IP adres te gebruiken. Terminal capabilities of %1 Terminal eigenschappen van %1 unknown onbekend none geen %1, registration failed: %2 %3 %1, registratie mislukt: %2 %3 %1, registration succeeded (expires = %2 seconds) %1, registratie geslaagd (duur = %2 seconden) %1, registration failed: STUN failure %1, registratie mislukt: STUN fout %1, de-registration succeeded: %2 %3 %1, deregistratie gesglaagd: %2 %3 %1, fetching registrations failed: %2 %3 %1, opvragen registraties mislukt: %2 %3 : you are not registered : u bent niet geregistreerd : you have the following registrations : u heeft de volgende registraties : fetching registrations... : opvragen registraties... Redirecting request to: %1 Verzoek doorgewezen naar: %1 invalid DTMF telephone event (%1) foutief DTMF signaal (%1) Redirecting call Gesprek doorverwezen User profile: Gebruikersprofiel: User: Gebruiker: Do you allow the call to be redirected to the following destination? Staat u toe dat u wordt doorverwezen naar de volgende bestemming? If you don't want to be asked this anymore, then you must change the settings in the SIP protocol section of the user profile. Als u deze vraag niet meer wilt zien, dan kunt u dit aangeven in de SIP protocol instellingen van uw gebruikersprofiel. Redirecting request Verzoek doorverwezen Do you allow the %1 request to be redirected to the following destination? Staat u toe dat het %1 verzoek wordt doorverwezen naar de volgende bestemming? Transferring call Gesprek doorverbonden Request to transfer call received from: Verzoek om gesprek door te verbinden van: Do you allow the call to be transferred to the following destination? Staat u toe dat u wordt doorverbonden naar de volgende bestemming? Info: Info: Warning: Waarschuwing: Critical: Ernstige fout: Firewall / NAT discovery... Firewall / NAT verkennen... Abort Afbreken Line %1 Lijn %1 Click the padlock to confirm a correct SAS. Klik op het hangslot om een juiste SAS te bevestigen. The remote user on line %1 disabled the encryption. De andere partij op lijn %1 heeft encryptie uitgeschakeld. Failed to start conference. Starten van conferentie mislukt. You can only run multiple profiles for different users. U kunt alleen meerdere profielen voor verschillende gebruikers starten. Line %1: incoming call for %2 Lijn %1: inkomend gesprek voor %2 Call transferred by %1 Gesprek doorverbonden door %1 Line %1: far end cancelled call. Lijn %1: andere partij heeft gesprek geannuleerd. Line %1: far end released call. Lijn %1: andere partij heeft gesprek beëindigd. Line %1: SDP answer from far end not supported. Lijn %1: SDP van andere partij wordt niet ondersteund. Line %1: SDP answer from far end missing. Lijn %1: SDP van andere partij ontbreekt. Line %1: Unsupported content type in answer from far end. Lijn %1: niet-ondersteunde "content type" ontvangen van andere partij. Line %1: no ACK received, call will be terminated. Lijn %1: geen ACK ontvangen, gesprek wordt afgebroken. Line %1: no PRACK received, call will be terminated. Lijn %1: geen PRACK ontvangen, gesprek wordt afgebroken. Line %1: PRACK failed. Lijn %1: PRACK mislukt. Line %1: failed to cancel call. Lijn %1: annuleren van gesprek is mislukt. Line %1: far end answered call. Lijn %1: gesprek beantwoord. Line %1: call failed. Lijn %1: gesprek mislukt. The call can be redirected to: U kunt het nogmaals proberen naar: Line %1: call released. Lijn %1: gesprek beëindigd. Line %1: call established. Lijn %1: gesprek verbonden. Accepted body types: Geaccepteerde "body" typen: Accepted encodings: Geaccepteerde encoderingen: Accepted languages: Geaccepteerde talen: Supported extensions: Ondersteunde extensies: End point type: Toesteltype: Line %1: call retrieve failed. Lijn %1: terughalen gesprek mislukt. Line %1: redirecting request to Lijn %1: verzoek doorverwezen naar Line %1: send DTMF %2 Lijn %1: stuur DTMF %2 Line %1: far end does not support DTMF telephone events. Lijn %1: andere partij ondersteund geen DTMF "telephone events". Line %1: received notification. Lijn %1: notificatie ontvangen. Event: %1 Gebeurtenis: %1 State: %1 Toestand: %1 Reason: %1 Reden: %1 Progress: %1 %2 Voortgang: %1 %2 Line %1: call transfer failed. Lijn %1: doorverbinden gesprek mislukt. Line %1: call successfully transferred. Lijn %1: doorverbinden gesprek geslaagd. Line %1: call transfer still in progress. Lijn %1: nog steeds bezig met doorverbinden gesprek. No further notifications will be received. Er komen geen notificaties meer. Line %1: transferring call to %2 Lijn %1: doorverbinden gesprek met %2 Transfer requested by %1 Verzoek tot doorverbinden door %1 Line %1: Call transfer failed. Retrieving original call. Lijn %1: doorverbinden mislukt. Oorspronkelijk gesprek wordt teruggehaald. Line %1: SAS confirmed. Lijn %1: SAS bevestigd. Line %1: SAS confirmation reset. Lijn %1: SAS bevestiging gewist. Line %1: call rejected. Lijn %1: gesprek afgewezen. Line %1: call redirected. Lijn %1: gesprek doorverwezen. Response on terminal capability request: %1 %2 Antwoord op verzoek om terminal eigenschappen: %1 %2 The following profiles are both for user %1 De volgende profielen zijn beiden voor gebruiker %1 Allowed requests: Toegestane verzoeken: Line %1: DTMF detected: Lijn %1: DTMF gedetecteerd: Failed to create a UDP socket (SIP) on port %1 Opzetten UDP socket (SIP) op port %1 mislukt Override lock file and start anyway? Wilt u het lock bestand overschrijven en opstarten? %1, voice mail status failure. %1, voice mail status fout. %1, voice mail status rejected. %1, voice mail status geweigerd. %1, voice mailbox does not exist. %1, voice mailbox bestaat niet. %1, voice mail status terminated. %1, voice mail status beëindigd. %1, STUN request failed: %2 %3 %1, STUN verzoek mislukt: %2 %3 %1, STUN request failed. %1, STUN verzoek mislukt. %1, de-registration failed: %2 %3 %1, deregistratie mislukt: %2 %3 Request to transfer call received. Verzoek om gesprek door te verbinden. If these are users for different domains, then enable the following option in your user profile (SIP protocol) Als dit gebruikers in verschillende domeinen zijn, dan moet u de volgende optie in uw gebruikersprofiel (SIP protocol) aanzetten Use domain name to create a unique contact header Gebruik domeinnaam voor een unieke contact header Failed to create a %1 socket (SIP) on port %2 SIP %1 poort kan niet geopend worden Accepted by network Geaccepteerd door netwerk Failed to save message attachment: %1 Opslaan van bijlage %1 mislukt Transferred by: %1 Doorverbonden door: %1 Cannot open web browser: %1 Web browser kan niet geopend worden: %1 Configure your web browser in the system settings. Configureer uw web browser in de systeeminstellingen. GetAddressForm Twinkle - Select address Twinkle - Kies adres Name Naam Type Type Phone Telefoon &Show only SIP addresses &Toon alleen SIP adressen Alt+S Alt+T Check this option when you only want to see contacts with SIP addresses, i.e. starting with "<b>sip:</b>". Vink deze optie aan als u alleen contacten met een SIP adres wilt zien, d.w.z. adressen die starten met "<b>sip:</b>". &Reload &Herladen Alt+R Alt+H Reload the list of addresses from KAddressbook. Haal de adressen opnieuw op uit KAddressbook. &OK &OK Alt+O Alt+O &Cancel Ann&uleren Alt+C Alt+U &KAddressBook &KAddressBook This list of addresses is taken from <b>KAddressBook</b>. Contacts for which you did not provide a phone number are not shown here. To add, delete or modify address information you have to use KAddressBook. Deze lijst met adressen komt uit <b>KAddressBook</b>. Contacten waarvoor u geen telefoonnummer heeft opgenomen staan niet in deze lijst. Om adresinformatie te wijzigen moet u KAddressBook gebruiken. &Local address book &Lokaal adresboek Remark Opmerkingen Contacts in the local address book of Twinkle. Adresgegevens uit het lokale adresboek van Twinkle. &Add To&evoegen Alt+A Alt+E Add a new contact to the local address book. Voeg een adres toe aan het lokale adresboek. &Delete &Verwijderen Alt+D Alt+V Delete a contact from the local address book. Verwijder een adres uit het lokale adresboek. &Edit &Bewerk Alt+E Alt+B Edit a contact from the local address book. Wijzig adresgegevens. <p>You seem not to have any contacts with a phone number in <b>KAddressBook</b>, KDE's address book application. Twinkle retrieves all contacts with a phone number from KAddressBook. To manage your contacts you have to use KAddressBook.<p>As an alternative you may use Twinkle's local address book.</p> <p>U heeft geen contacten met een telefoonnummer in <b>KAddressBook</b>, KDE's adresboek applicatie. Twinkle haalt alle contacten met een telefoonnummer uit KAdressBook. Om uw contacten te beheren, moet u KAddressbook gebruiken.</p> <p>Als alternatief kunt u het lokale adresboek van Twinkle gebruiken.</p> GetProfileNameForm Twinkle - Profile name Twinkle - Profielnaam &OK &OK &Cancel Ann&uleren Enter a name for your profile: Voer de naam voor uw profiel in: <b>The name of your profile</b> <br><br> A profile contains your user settings, e.g. your user name and password. You have to give each profile a name. <br><br> If you have multiple SIP accounts, you can create multiple profiles. When you startup Twinkle it will show you the list of profile names from which you can select the profile you want to run. <br><br> To remember your profiles easily you could use your SIP user name as a profile name, e.g. <b>example@example.com</b> <b>De naam van uw profiel</b> <br><br> Een profiel bevat uw gebruikersinstellingen, bijv. uw gebruikersnaam en paswoord. U moet elk profiel een naam geven. <br><br> Als u meerde SIP accounts heeft, dan kunt u meerdere profielen maken. Als u Twinkle opstart dan krijgt u een lijst met alle profielnamen te zien. Uit deze lijst kunt u kiezen welk profiel u wilt starten. <br><br> Om uw gebruikersprofielen makkelijk uit elkaar te houden kunt u uw SIP gebruikersnaam als profielnaam gebruiken, bijv. <b>example@example.com</b> Cannot find .twinkle directory in your home directory. .twinkle folder kan niet gevonden worden in uw thuis folder. Profile already exists. Profiel bestaat al. Rename profile '%1' to: Hernoem profiel '%1' naar: HistoryForm Twinkle - Call History Twinkle - Gesprekshistorie Time Tijd From/To Van/Naar Subject Onderwerp Status Status Call details Gespreksdetails Details of the selected call record. Details van het geselecteerde gesprek. View Toon &Incoming calls &Inkomende gesprekken Alt+I Alt+I Check this option to show incoming calls. Vink deze optie aan om inkomende gesprekken te tonen. &Outgoing calls &Uitgaande gesprekken Alt+O Alt+U Check this option to show outgoing calls. Vink deze optie aan om uitgaande gesprekken te tonen. &Answered calls Be&antwoorde gesprekken Alt+A Alt+A Check this option to show answered calls. Vink deze optie aan om beantwoorde gesprekken te tonen. &Missed calls Ge&miste gesprekken Alt+M Alt+M Check this option to show missed calls. Vink deze optie aan om gemiste gesprekken te tonen. Current &user profiles only Alleen actieve &profielen Alt+U Alt+P Check this option to show only calls associated with this user profile. Vink deze optie aan om alleen gesprekken behorende bij de nu actieve profielen te tonen. C&lear &Wis Alt+L Alt+W <p>Clear the complete call history.</p> <p><b>Note:</b> this will clear <b>all</b> records, also records not shown depending on the checked view options.</p> <p>Wis de gehele gespreksgeschiedenig.</p> <p><b>Noot:</b> hiermee wist u <b>alle</b> gespreksgegevens, ook gegevens die niet getoond worden afhankelijk van de toon-opties.</p> Alt+C Alt+S Close this window. Sluit dit venster. Call start: Begin: Call answer: Beantwoord: Call end: Eind: Call duration: Duur: Direction: Richting: From: Van: To: Naar: Reply to: Antwoord aan: Referred by: Doorverbonden door: Subject: Onderwerp: Released by: Beëindigd door: Status: Status: Far end device: Toestel partner: User profile: Gebruikersprofiel: conversation gesprek Call... Bel... Delete Wis Re: Antw: In/Out In/Uit Call selected address. Bel geselecteerd adres. Clo&se &Sluiten Alt+S Alt+S &Call &Bel Number of calls: Aantal gesprekken: ### Total call duration: Totale duur: IncomingCallPopup %1 calling InviteForm Twinkle - Call Twinkle - Bellen &To: &Aan: Optionally you can provide a subject here. This might be shown to the callee. Hier kunt u optioneel een onderwerp invoeren. Dit kan getoond worden aan de gebelde partij. Address book Adresboek Select an address from the address book. Kies een adres uit het adresboek. The address that you want to call. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Het adres dat u wilt bellen. Dit kan een volledig SIP adres zijn zoals <b>sip:example@example.com</b> of alleen een gebruikersnaam of telefoonnummer. Als u geen volledig adres invoert, dan zal Twinkle het adres afmaken met de waarde die u voor domein heeft ingevuld in uw gebruikersprofiel. The user that will make the call. Het gebruikersprofiel waarmee u het gesprek wilt maken. &Subject: O&nderwerp: &From: &Van: &OK &OK &Cancel Ann&uleren &Hide identity &Identiteit verbergen Alt+H Alt+I <p> With this option you request your SIP provider to hide your identity from the called party. This will only hide your identity, e.g. your SIP address, telephone number. It does <b>not</b> hide your IP address. </p> <p> <b>Warning:</b> not all providers support identity hiding. </p> <p> Met deze optie verzoekt u uw SIP provider om uw identiteit verborgen te houden voor degene die u belt. Alleen uw SIP adres of telefoonnummer blijft geheim. Uw IP adres wordt <b>niet</b> verborgen. </p> <p> <b>Waarschuwing:</b> niet alle providers ondersteunen het verbergen van uw identiteit. </p> Not all SIP providers support identity hiding. Make sure your SIP provider supports it if you really need it. Niet alle SIP providers ondersteunen het verbergen van uw identiteit. Verzeker u ervan dat uw SIP provider dit ondersteunt als u dit nodig heeft. F10 F10 LogViewForm Twinkle - Log Twinkle - Log &Close &Sluiten Alt+C Alt+S C&lear &Wis Alt+L Alt+W Clear the log window. This does <b>not</b> clear the log file itself. Wis het log venster. Hiermee wist u <b>niet</b> het log bestand zelf. Contents of the current log file (~/.twinkle/twinkle.log) Inhoud van het log bestand (~/.twinkle/twinkle.log) MessageForm Twinkle - Instant message Twinkle - Instant bericht &To: &Aan: The user that will send the message. De gebruiker die het bericht stuurt. The address of the user that you want to send a message. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Het adres van de persoon aan wie u een bericht wilt sturen. Dit kan een volledig SIP adres zijn zoals <b>sip:example@example.com</b> of een een telefoonnummer. Als u geen volledig adres opgeeft, dan zal Twinkle dit adres compleet maken door de domeinnaam uit uw gebruikersprofiel toe te voegen. Address book Adresboek Select an address from the address book. Kies een adres uit het adresboek. &User profile: &Gebruikersprofiel: Conversation Conversatie The exchanged messages. De uitgewisselde berichten. Type your message here and then press "send" to send it. Typ uw bericht en druk op "zend" om het te verzenden. &Send &Zend Alt+S Alt+Z Send the message. Zend het bericht. Delivery failure Afleverfout Delivery notification Aflevernotificatie Instant message toolbar Instant berichten Send file... Zend bestand... Send file Zend bestand image size is scaled down in preview plaatje is verkleind voor preview Open with %1... Openen met %1... Open with... Openen met... Save attachment as... Bijlage opslaan als... File already exists. Do you want to overwrite this file? Bestand bestaat al. Wilt u dit bestand overschrijven? Failed to save attachment. Opslaan bijlage mislukt. %1 is typing a message. %1 schrijft een bericht. F10 F10 Size Lengte MessageFormView sending message bericht wordt verstuurd MphoneForm Twinkle Twinkle The address that you want to call. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Het adres dat u wilt bellen. Dit kan een volledig SIP adres zijn zoals <b>sip:example@example.com</b> of alleen een gebruikersnaam of telefoonnummer. Als u geen volledig adres invoert, dan zal Twinkle het adres afmaken met de waarde die u voor domein heeft ingevuld in uw gebruikersprofiel. The user that will make the call. Het gebruikersprofiel waarmee u het gesprek wilt maken. &User: &Gebruiker: Dial Bel Dial the address. Bel het adres. Address book Adresboek Select an address from the address book. Kies een adres uit het adresboek. Auto answer indication. Indicate automatisch beantwoord. Call redirect indication. Indicatie doorverwijzen. Do not disturb indication. Indicatie niet storen. Missed call indication. Indicatie gemiste gesprekken. Registration status. Registratiestatus. Display Scherm Line status Lijnstatus Line &1: Lijn &1: Alt+1 Alt+1 Click to switch to line 1. Kliek hier om over te schakelen naar lijn 1. From: Van: To: Naar: Subject: Onderwerp: idle vrij Call is on hold Gesprek staat in de wacht Voice is muted Geluid is onderdrukt Conference call Conferentiegesprek Transferring call Gesprek doorverbonden <p> The padlock indicates that your voice is encrypted during transport over the network. </p> <h3>SAS - Short Authentication String</h3> <p> Both ends of an encrypted voice channel receive the same SAS on the first call. If the SAS is different at each end, your voice channel may be compromised by a man-in-the-middle attack (MitM). </p> <p> If the SAS is equal at both ends, then you should confirm it by clicking this padlock for stronger security of future calls to the same destination. For subsequent calls to the same destination, you don't have to confirm the SAS again. The padlock will show a check symbol when the SAS has been confirmed. </p> <p> Het hangslot geeft aan dat het geluidskanaal versleuteld is tijdens transport over het netwerk. </p> <h3>SAS - Short Authentication String</h3> <p> Beide kanten van een versleuteld geluidskanaal ontvangen dezelfde SAS bij het eerste gesprek. Als de SAS niet hetzelfde is aan beide kanten, dan kan uw geluidskanaal gecompromiteerd zijn door een "man-in-the-middle" aanval (MitM). </p> <p> Als de SAS aan beide kanten hetzelfde is, dan moet u die bevestigen door op het hangslot te klikken voor hogere veiligheid van toekomstige gesprekken naar dezelfde bestemming. Voor toekomstige gesprekken naar deze bestemming, hoeft u de SAS dan niet nogmaals te bevestigen. Het hangslot toont een verificatie symbool als de SAS bevestigd is. </p> Short authentication string Short authentication string Audio codec Audio codec 0:00:00 0:00:00 Call duration Gespreksduur Line &2: Lijn &2: Alt+2 Alt+2 Click to switch to line 2. Klik hier om over te schakelen naar lijn 2. &File &Bestand &Edit Be&werk C&all Ge&sprek Activate line Activeer lijn &Registration &Registratie &Services &Diensten &View &Toon &Help &Help Call Toolbar Gespreksbalk Quit Afsluiten &Quit &Aflsuiten Ctrl+Q Ctrl+Q About Twinkle Over Twinkle &About Twinkle Over &Twinkle Call someone Iemand bellen F5 F5 Answer incoming call Beantwoord een binnenkomend gesprek F6 F6 Release call Beëindig een gesprek Reject incoming call Wijs een binnenkomend gesprek af F8 F8 Put a call on hold, or retrieve a held call Zet een gesprek in de wacht, of haal een gesprek uit de wacht Redirect incoming call without answering Verwijs een binnenkomend gesprek naar een andere bestemming zonder te antwoorden Open keypad to enter digits for voice menu's Open een toetsenbord om cijfers in te voeren voor een stem gestuurd menu Register Registreren &Register &Registreren Deregister Deregistreren &Deregister &Deregistreren Deregister this device Deregistreer dit apparaat Show registrations Toon registraties &Show registrations &Toon registraties Terminal capabilities Terminal eigenschappen Request terminal capabilities from someone Vraag terminal eigenschappen van iemand op Do not disturb Niet storen &Do not disturb &Niet storen Call &redirection... &Doorverwijzen... Repeat last call Herhaal het laatste gesprek F12 F12 About Qt Over Qt About &Qt Over &Qt &User profile... &Gebruikersprofiel... Join two calls in a 3-way conference Verbind twee gesprekken in een 3-weg conferentie Mute a call Onderdruk het geluid Transfer call Gesprek doorverbinden &System settings... &Systeeminstellingen... Deregister all Alles deregistreren Deregister &all &Alles deregistreren Deregister all your registered devices Deregistreer al uw geregistreerde apparaten Auto answer Automatisch beantwoorden &Auto answer &Automatisch beantwoorden Log Log &Log... &Log... Call &history... Gespreks&historie... F9 F9 Change user ... Wijzig gerbuiker... &Change user ... &Wijzig gebruiker... Activate or de-activate users Activeer of de-actieveer gebruikers What's This? Wat is dit? What's &This? &Wat is dit? Shift+F1 Shift+F1 Line 1 Lijn 1 Line 2 Lijn 2 idle No need to translate sas No need to translate g711a/g711a No need to translate sip:from No need to translate sip:to No need to translate subject No need to translate photo No need to translate dialing bellen attempting call, please wait gesprek opzetten, wacht aub incoming call inkomend gesprek establishing call, please wait gesprek verbinden, wacht aub established verbonden established (waiting for media) verbonden (wacht op media) releasing call, please wait gesprek afbreken, wacht aub unknown state onbekend Voice is encrypted Geluid is versleuteld Click to confirm SAS. Klik om SAS te bevestigen. Click to clear SAS verification. Klik om SAS bevestiging te wissen. Registration status: Registratiestatus: Registered Geregistreeerd Failed Mislukt Not registered Niet geregistreerd No users are registered. Niemand is geregistreerd. Do not disturb active for: Niet storen actief voor: Redirection active for: Doorverwijzen actief voor: Auto answer active for: Automatisch beantwoorden actief voor: Do not disturb is not active. Niet storen is niet actief. Redirection is not active. Doorverwijzen is niet actief. Auto answer is not active. Automatisch beantwoorden is niet actief. You have no missed calls. Geen gemiste gesprekken. You missed 1 call. U heeft 1 gemist gesprek. You missed %1 calls. U heeft %1 gemiste gesprekken. Click to see call history for details. Klik hier om de gesprekshistorie te zien. Starting user profiles... Opstarten gebruikersprofielen... You can only run multiple profiles for different users. U kunt alleen meerdere profielen voor verschillende gebruikers starten. You have changed the SIP UDP port. This setting will only become active when you restart Twinkle. U heeft de SIP UDP poort gewijzigd. Deze instelling wordt pas actief als u Twinkle opnieuw opstart. User: Gebruiker: Call: Bel: The following profiles are both for user %1 De volgende profielen zijn beiden voor gebruiker %1 Visual indication of line state. Visuele indicatie van de lijnstatus. Call redirection Doorverwijzen User profile Gebruikersprofiel System settings Systeeminstellingen Call history Gesprekshistorie &Call: Label in front of combobox to enter address B&el: Esc Esc Transfer consultation Ruggespraak doorverbinden Hide identity Identiteit verbergen Click to show registrations. Klik om registraties te tonen. %1 new, 1 old message %1 nieuw, 1 oud bericht %1 new, %2 old messages %1 nieuw, %2 oude berichten 1 new message 1 nieuw bericht %1 new messages %1 nieuwe berichten 1 old message 1 oud bericht %1 old messages %1 oude berichten Messages waiting Er zijn berichten No messages Geen berichten <b>Voice mail status:</b> <b>Voice mail status:</b> Failure Fout Unknown Onbekend Click to access voice mail. Klik voor toegang to voice mail. Click to activate/deactivate Klik om te activeren/deactiveren Click to activate Klik om te activeren not provisioned niet ingesteld You must provision your voice mail address in your user profile, before you can access it. Voor toegang tot voice mail moet u eerst uw voice mail nummer in uw gebruikersprofiel invullen. The line is busy. Cannot access voice mail. De lijn is bezet. Geen toegang tot voice mail. The voice mail address %1 is an invalid address. Please provision a valid address in your user profile. Het voice mail nummer %1 is ongeldig. Vul een geldig nummer in in uw gebruikersprofiel. Call toolbar text Bel &Call... call menu text &Bel... Answer toolbar text Antw &Answer menu text &Antwoord Bye toolbar text Einde &Bye menu text &Einde Reject toolbar text Afwijzen &Reject menu text A&fwijzen Hold toolbar text Wacht &Hold menu text &Wacht Redirect toolbar text Verwijs R&edirect... menu text &Verwijs... Dtmf toolbar text Dtmf &Dtmf... menu text Dt&mf... &Terminal capabilities... menu text &Terminal eigenschappen... Redial toolbar text Herh &Redial menu text &Herhaal Conf toolbar text Conf &Conference menu text &Conferentie Mute toolbar text Stil &Mute menu text &Stil Xfer toolbar text Xfer Trans&fer... menu text &Doorverbinden... Voice mail Voice mail &Voice mail &Voice mail Access voice mail Bel voice mail F11 F11 Message waiting indication. Voice mail status. Buddy list Vrienden &Message &Bericht Msg Bericht Instant &message... Instant &bericht... Instant message Instant bericht &Call... &Bel... &Edit... Be&werk... &Delete &Verwijderen O&ffline O&ffline &Online &Online &Change availability &Wijzig beschikbaarheid &Add buddy... &Vriend toevoegen... Failed to save buddy list: %1 Opslaan van vriendenlijst is mislukt: %1 You can create a separate buddy list for each user profile. You can only see availability of your buddies and publish your own availability if your provider offers a presence server. Voor elk gebruikersprofiel kunt u een vriendenlijst aanleggen. Om de beschikbaarheid van uw vrienden te zien en uw eigen beschikbaarheid te publiceren, moet uw provider over een presence server beschikken. &Buddy list &Vrienden &Display &Scherm F10 F10 Diamondcard Manual Handleiding &Manual &Handleinding Sign up Aanmelden &Sign up... &Aanmelden... Recharge... Opwaarderen... Balance history... Balansoverzicht... Call history... Gesprekkenoverzicht... Admin center... Admin center... Recharge Opwaarderen Balance history Balansoverzicht Admin center Admin center Call Bel &Answer &Antwoord Answer &Bye &Einde Bye Einde &Reject A&fwijzen Reject Afwijzen &Hold &Wacht Hold Wacht R&edirect... &Verwijs... Redirect Verwijs &Dtmf... Dt&mf... Dtmf Dtmf &Terminal capabilities... &Terminal eigenschappen... &Redial &Herhaal Redial Herh &Conference &Conferentie Conf Conf &Mute &Stil Mute Stil Trans&fer... &Doorverbinden... Xfer Xfer NumberConversionForm &Match expression: &Match expressie: &Replace: &Vervang: Perl style format string for the replacement number. Formaat string (Perl syntax) voor het vervangen van het nummer in. Perl style regular expression matching the number format you want to modify. Reguliere expressie (Perl syntax) voor het matchen van het nummerformaat dat u wilt modificeren. &OK &OK Alt+O Alt+O &Cancel Ann&uleren Alt+C Alt+U Twinkle - Number conversion Twinkle - Nummerconversie Match expression may not be empty. Match expressie mag niet leeg zijn. Replace value may not be empty. Vervangwaarde mag niet leeg zijn. Invalid regular expression. Ongeldige reguliere expressie. RedirectForm Twinkle - Redirect Twinkle - Doorverwijzen Redirect incoming call to Verwijs inkomend gesprek naar You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. U kan tot 3 bestemmingen invoeren waarnaar u een gesprek wilt doorverwijzen. Als de eerste bestemming niet opneemt, dan wordt de tweede bestemming geprobeerd enz. &3rd choice destination: &3e bestemming: &2nd choice destination: &2e bestemming: &1st choice destination: &1e bestemming: Address book Adresboek Select an address from the address book. Kies een adres uit het adresboek. &OK &OK &Cancel Ann&uleren F10 F10 F12 F12 F11 F11 SelectNicForm Twinkle - Select NIC Twinkle - Kies NIC Select the network interface/IP address that you want to use: Kies de netwerk interface/IP adres die u wilt gebruiken: You have multiple IP addresses. Here you must select which IP address should be used. This IP address will be used inside the SIP messages. U heeft meerdere IP adressen. Hier moet u aangeven welk IP adres gebruikt moet worden. Dit adres wordt gebruikt in de SIP berichten. Set as default &IP Default &IP Alt+I Alt+I Make the selected IP address the default IP address. The next time you start Twinkle, this IP address will be automatically selected. Het geselecteerde IP adres wordt het default IP adres. De volgende keer dat u Twinkle start wordt dit IP adres automatisch geslecteerd. Set as default &NIC Default &NIC Alt+N Alt+N Make the selected network interface the default interface. The next time you start Twinkle, this interface will be automatically selected. De geslecteerde netwerk interface wordt de default interface. De volgende keer dat u Twinkle opstart wordt deze interface automatisch geslecteerd. &OK &OK Alt+O Alt+O If you want to remove or change the default at a later time, you can do that via the system settings. Als u de default later wilt verwijderen of wijzigen, dan kan dat via de systeeminstellingen. SelectProfileForm Twinkle - Select user profile Twinkle - Kies gebruikersprofiel Select user profile(s) to run: Selecteer de gebruikersprofielen die u wilt activeren: User profile Gebruikersprofiel Tick the check boxes of the user profiles that you want to run and press run. Vink de gebruikersprofielen aan die u wilt activeren. &New &Nieuw Alt+N Alt+N Create a new profile with the profile editor. Maak een nieuw profiel met de profielen editor. &Wizard Wi&zard Alt+W Alt+Z Create a new profile with the wizard. Maak een nieuw profiel met de wizard. &Edit Be&werk Alt+E Alt+W Edit the highlighted profile. Bewerk het huidige profiel. &Delete &Verwijderen Alt+D Alt+V Delete the highlighted profile. Verwijder het huidige profiel. Ren&ame &Hernoemen Alt+A Alt+H Rename the highlighted profile. Hernoem het huidige profiel. &Set as default &Default Alt+S Alt+D Make the selected profiles the default profiles. The next time you start Twinkle, these profiles will be automatically run. Markeer de geselecteerde profielen als default profielen. De volgende keer dat u Twinkle opstart, zullen deze profielen automatisch gestart worden. &Run &Start Alt+R Alt+S Run Twinkle with the selected profiles. Start Twinkle met de geselecteerde profielen. S&ystem settings S&ysteeminstellingen Alt+Y Alt+Y Edit the system settings. Wijzig de systeeminstellingen. &Cancel Ann&uleren Alt+C Alt+U <html>Before you can use Twinkle, you must create a user profile.<br>Click OK to create a profile.</html> <html>Voordat u Twinkle kunt gebruiken, moet u een gebruikerspofiel maken.<br>Klik op OK om een gebruikersprofiel te maken.</html> <html>You can use the profile editor to create a profile. With the profile editor you can change many settings to tune the SIP protocol, RTP and many other things.<br><br>Alternatively you can use the wizard to quickly setup a user profile. The wizard asks you only a few essential settings. If you create a user profile with the wizard you can still edit the full profile with the profile editor at a later time.<br><br>Choose what method you wish to use.</html> <html>U kunt de profieleditor gebruiken om een gebruikersprofiel te maken. Met de profieleditor kunt u diverse instellingen met betrekking tot het SIP protocol, RTP en vele andere zaken wijzigen.<br><br>Met de wizard kunt u snel een gebruikersprofiel maken. De wizard vraagt u alleen om een aantal essentiële instellingen. Als u een gebruikersprofiel met de wizard maakt, dan kun u deze op een later tijdstip alsnog met de profieleditor wijzigen.<br><br>Kies op welke wijze u een gebruikersprofiel wilt maken.</html> <html>Next you may adjust the system settings. You can change these settings always at a later time.<br><br>Click OK to view and adjust the system settings.</html> <html>U kunt nu de systeeminstellingen wijzigen. Deze instellingen kunt u altijd wijzigen op een later tijdstip.<br><br>Klik op OK om de systeeminstellingen te bekijken en eventueel te wijzigen.</html> You did not select any user profile to run. Please select a profile. U heeft geen gebruikersprofiel geselecteerd. Kies eerst een gebruikersprofiel. Are you sure you want to delete profile '%1'? Weet u zeker dat u profiel '%1' wilt verwijderen? Delete profile Verwijder profiel Failed to delete profile. Profiel verwijderen mislukt. Failed to rename profile. Profiel hernoemen mislukt. <p>If you want to remove or change the default at a later time, you can do that via the system settings.</p> <p>Als u de default later wilt verwijderen of wijzigen, dan kunt u dat doen via de systeeminstellingen.</p> Cannot find .twinkle directory in your home directory. .twinkle folder kan niet gevonden worden in uw thuis folder. &Profile editor &Profieleditor Create profile Maak gebruikersprofiel Ed&itor Ed&itor Alt+I Alt+I Dia&mondcard Dia&mondcard Alt+M Alt+M Modify profile Bewerk gebruikersprofiel Startup profile Starten gebruikersprofiel <html>You can use the profile editor to create a profile. With the profile editor you can change many settings to tune the SIP protocol, RTP and many other things.<br><br>Alternatively you can use the wizard to quickly setup a user profile. The wizard asks you only a few essential settings. If you create a user profile with the wizard you can still edit the full profile with the profile editor at a later time.<br><br>You can create a Diamondcard account to make worldwide calls to regular and cell phones.<br><br>Choose what method you wish to use.</html> <html>U kunt de profieleditor gebruiken om een gebruikersprofiel te maken. Met de profieleditor kunt u diverse instellingen met betrekking tot het SIP protocol, RTP en vele andere zaken wijzigen.<br><br>Met de wizard kunt u snel een gebruikersprofiel maken. De wizard vraagt u alleen om een aantal essentiële instellingen. Als u een gebruikersprofiel met de wizard maakt, dan kun u deze op een later tijdstip alsnog met de profieleditor wijzigen.<br><br>U kunt een Diamondcard account aanmaken waarmee u wereldwijd kunt bellen naar vaste en mobiele telefoonnummers.<br><br>Kies op welke wijze u een gebruikersprofiel wilt maken.</html> &Diamondcard &Diamondcard Create a profile for a Diamondcard account. With a Diamondcard account you can make worldwide calls to regular and cell phones. Maak een gebruikersprofiel voor een Diamondcard account. Met een Diamondcard account kunt u wereldwijd bellen naar vaste en mobiele telefoonnummers. Create a profile for a Diamondcard account. With a Diamondcard account you can make worldwide calls to regular and cell phones and send SMS messages. Maak een gebruikersprofiel voor een Diamondcard account. Met een Diamondcard account kunt u wereldwijd bellen naar vaste en mobiele telefoonnummers en SMS berichten versturen. <html>You can use the profile editor to create a profile. With the profile editor you can change many settings to tune the SIP protocol, RTP and many other things.<br><br>Alternatively you can use the wizard to quickly setup a user profile. The wizard asks you only a few essential settings. If you create a user profile with the wizard you can still edit the full profile with the profile editor at a later time.<br><br>You can create a Diamondcard account to make worldwide calls to regular and cell phones and send SMS messages.<br><br>Choose what method you wish to use.</html> <html>U kunt de profieleditor gebruiken om een gebruikersprofiel te maken. Met de profieleditor kunt u diverse instellingen met betrekking tot het SIP protocol, RTP en vele andere zaken wijzigen.<br><br>Met de wizard kunt u snel een gebruikersprofiel maken. De wizard vraagt u alleen om een aantal essentiële instellingen. Als u een gebruikersprofiel met de wizard maakt, dan kun u deze op een later tijdstip alsnog met de profieleditor wijzigen.<br><br>U kunt een Diamondcard account aanmaken waarmee u wereldwijd kunt bellen naar vaste en mobiele telefoonnummers en SMS berichten versturen.<br><br>Kies op welke wijze u een gebruikersprofiel wilt maken.</html> <html>You can use the profile editor to create a profile. With the profile editor you can change many settings to tune the SIP protocol, RTP and many other things.<br><br>Alternatively you can use the wizard to quickly setup a user profile. The wizard asks you only a few essential settings. If you create a user profile with the wizard you can still edit the full profile with the profile editor at a later time.<br><br> You can create a Diamondcard account to make worldwide calls to regular and cell phones and send SMS messages.<br><br> Choose what method you wish to use.</html> SelectUserForm Twinkle - Select user Twinkle - Kies gebruiker &Cancel Ann&uleren Alt+C Alt+U &Select all &Selecteer alles Alt+S Alt+S &OK &OK Alt+O Alt+O C&lear all &Wis alles Alt+L Alt+W User Gebruiker Register Registreren Select users that you want to register. Selecteer welke gebruikers u wilt registreren. Deregister Deregistreren Select users that you want to deregister. Kies de gebruikers die u wilt deregistreren. Deregister all devices Deregistreer alle apparaten Select users for which you want to deregister all devices. Kies de gebruikers voor wie u alle apparaten wilt deregistreren. Do not disturb Niet storen Select users for which you want to enable 'do not disturb'. Kies de gebruikers voor wie u 'niet storen' wilt aanzetten. Auto answer Automatisch beantwoorden Select users for which you want to enable 'auto answer'. Kies de gebruikers voor wie u 'automatisch beantwoorden' wilt aanzetten. purpose No need to translate SendFileForm Twinkle - Send File Twinkle - Zend bestand Select file to send. Kies het bestand dat u wilt zenden. &File: &Bestand: &Subject: O&nderwerp: &OK &OK Alt+O &Cancel Ann&uleren Alt+C File does not exist. Bestand bestaat niet. Send file... Zend bestand... SrvRedirectForm Twinkle - Call Redirection Twinkle - Doorverwijzen User: Gebruiker: There are 3 redirect services:<p> <b>Unconditional:</b> redirect all calls </p> <p> <b>Busy:</b> redirect a call if both lines are busy </p> <p> <b>No answer:</b> redirect a call when the no-answer timer expires </p> Er zijn 3 doorverwijsdiensten:<p> <b>Onvoorwaardelijk:</b> alle inkomende gesprekken worden doorverwezen </p> <p> <b>Bezet:</b> een inkomend gesprek wordt doorverwezen als beide lijnen bezet zijn </p> <p> <b>Geen antwoord:</b> een inkomend gesprek wordt doorverwezen als u niet opneemt </p> &Unconditional O&nvoorwaardelijk &Redirect all calls &Alle gesprekken doorverwijzen Alt+R Alt+A Activate the unconditional redirection service. Activeer de onvoorwaardelijk doorverwijzen. Redirect to Doorverwijzen naar You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. U kan tot 3 bestemmingen invoeren waarnaar u een gesprek wilt doorverwijzen. Als de eerste bestemming niet opneemt, dan wordt de tweede bestemming geprobeerd enz. &3rd choice destination: &3e bestemming: &2nd choice destination: &2e bestemming: &1st choice destination: &1e bestemming: Address book Adresboek Select an address from the address book. Kies een adres uit het adresboek. &Busy &Bezet &Redirect calls when I am busy Gesprekken &doorverwijzen als alle lijnen bezet zijn Activate the redirection when busy service. Activeer doorverwijzen bij bezet. &No answer Geen &antwoord &Redirect calls when I do not answer Gesprekken &doorverwijzen als ik niet antwoord Activate the redirection on no answer service. Activeer doorverwijzen bij geen antwoord. &OK &OK Alt+O Alt+O Accept and save all changes. Sla de huidige instellingen op. &Cancel Ann&uleren Alt+C Alt+U Undo your changes and close the window. Maak uw wijzigingen ongedaan en sluit het venster. You have entered an invalid destination. U heeft een ongeldige bestemming ingevoerd. F10 F10 F11 F11 F12 F12 SysSettingsForm Twinkle - System Settings Twinkle - Systeeminstellingen General Algemeen Audio Audio Ring tones Ring tones Address book Adresboek Network Netwerk Log Log Select a category for which you want to see or modify the settings. Kies de categorie waarvoor u instellingen wilt wijzigen. Sound Card Geluidskaart Select the sound card for playing the ring tone for incoming calls. Kies de geluidskaart voor het afspelen van de ring tone bij een binnenkomend gesprek. Select the sound card to which your microphone is connected. Kies de geluidskaart waarmee uw microfoon is verbonden. Select the sound card for the speaker function during a call. Kies de geluidskaart waarmee uw speaker of headset verbonden is. &Speaker: &Speaker: &Ring tone: &Ring tone: Other device: Ander apparaat: &Microphone: &Microfoon: When using ALSA, it is not recommended to use the default device for the microphone as it gives poor sound quality. Als u ALSA gebruikt, dan is het niet aan te raden om het default apparaat als microfoon te gebruiken omdat die een matige geluidskwaliteit heeft. Reduce &noise from the microphone &Verminder ruis van de microfoon Alt+N Alt+V Recordings from the microphone can contain noise. This could be annoying to the person on the other side of your call. This option removes soft noise coming from the microphone. The noise reduction algorithm is very simplistic. Sound is captured as 16 bits signed linear PCM samples. All samples between -50 and 50 are truncated to 0. Opnames van de microfoon kunnen ruis bevatten. Dit kan vervelend zijn voor de persoon aan de andere kant van de lijn. Deze instelling kan zachte ruis van de microfoon verwijderen. Het algoritme om ruis te verminderen is erg simplistisch. Geluid wordt gedigitaliseerd als 16 bits lineaire PCM samples. Alle samples tussen de waarden -50 en 50 worden afgerond naar 0. Advanced Expert instellingen OSS &fragment size: OSS &fragment grootte: 16 16 32 32 64 64 128 128 256 256 The ALSA play period size influences the real time behaviour of your soundcard for playing sound. If your sound frequently drops while using ALSA, you might try a different value here. De ALSA afspeel periode grootte is van invloed op het real time gedrag van uw geluidskaart bij het afspelen van geluid. Als het geluid hapert bij het gebruik van ALSA, dan kunt u een andere waarde proberen. ALSA &play period size: ALSA afspeel-&periode grootte: &ALSA capture period size: &ALSA opneem-periode grootte: The OSS fragment size influences the real time behaviour of your soundcard. If your sound frequently drops while using OSS, you might try a different value here. De OSS fragment grootte is van invloed op het real time gedrag van uw geluidskaart. Als het geluid hapert bij het gebruik van OSS, dan kunt u een andere waarde proberen. The ALSA capture period size influences the real time behaviour of your soundcard for capturing sound. If the other side of your call complains about frequently dropping sound, you might try a different value here. De ALSA opneem-periode grootte is van invloed op het real time gedrag van uw geluidskaart bij het opnemen van geluid. Als het geluid aan de andere kant van de lijn hapert bij het gebruik van ALSA, dan kunt u een andere waarde proberen. &Max log size: &Max log grootte: The maximum size of a log file in MB. When the log file exceeds this size, a backup of the log file is created and the current log file is zapped. Only one backup log file will be kept. De maximum omvang van het log bestand in MB. Als de log file groter wordt dan deze omvang, dan wordt een backup van de log file gemaakt en wordt de huidige log file gewist. Er wordt slechts één backup log bestand bewaard. MB MB Log &debug reports Log &debug meldingen Alt+D Alt+D Indicates if reports marked as "debug" will be logged. Plaats "debug" meldingen in het log bestand. Log &SIP reports Log &SIP meldingen Alt+S Alt+S Indicates if SIP messages will be logged. Plaats SIP meldingen in het log bestand. Log S&TUN reports Log S&TUN meldingen Alt+T Alt+T Indicates if STUN messages will be logged. Plaats STUN meldingen in het log bestand. Log m&emory reports Log g&eheugen meldingen Alt+E Alt+E Indicates if reports concerning memory management will be logged. Plaats meldingen met betrekking tot geheugenbeheer in het log bestand. System tray Systeemvak Create &system tray icon on startup Maak een icoon in het &systeemvak bij opstarten Enable this option if you want a system tray icon for Twinkle. The system tray icon is created when you start Twinkle. Met deze optie wordt er een icoon in het systeemvak geplaatst bij het opstarten van Twinkle. &Hide in system tray when closing main window &Verbergen in systeemvak bij sluiten van het hoofdvenster Alt+H Alt+V Enable this option if you want Twinkle to hide in the system tray when you close the main window. Met deze optie verbert Twinkle zich in het systeemvak als u het hoofdvenster sluit. Startup Opstarten Next time you start Twinkle, this IP address will be automatically selected. This is only useful when your computer has multiple and static IP addresses. De volgende keer dat u Twinkle opstart wordt dit IP adres automatisch geselecteerd. Dit is alleen handig als uw computer meerdere statische IP adressen heeft. Default &IP address: Default &IP adres: Next time you start Twinkle, the IP address of this network interface be automatically selected. This is only useful when your computer has multiple network devices. De volgende keer dat u Twinkle opstart wordt het IP adres van deze netwerk interface automatisch geselecteerd. Dit is alleen handig als uw computer meerdere netwerk interfaces heeft. Default &network interface: Default &netwerk interface: S&tartup hidden in system tray Verborgen ops&tarten in systeemvak Next time you start Twinkle it will immediately hide in the system tray. This works best when you also select a default user profile. De volgende keer dat u Twinkle opstart, zal Twinkle zich onmiddelijk verbergen in het systeemvak. Dit werkt het beste als u ook een default gebruikersprofiel selecteert. Default user profiles Default gebruikersprofielen If you always use the same profile(s), then you can mark these profiles as default here. The next time you start Twinkle, you will not be asked to select which profiles to run. The default profiles will automatically run. Als u altijd dezelfde gebruikersprofielen gebruikt, dan kunt u deze profielen als default markeren. De volgdende keer dat u Twinkle opstart, wordt u dan niet meer gevraagd om een gebruikers profiel te kiezen. De default profielen worden automatisch opgestart. Services Diensten Call &waiting &Wisselgesprek Alt+W Alt+W With call waiting an incoming call is accepted when only one line is busy. When you disable call waiting an incoming call will be rejected when one line is busy. Met wisselgesprek kunt een inkomend gesprek ontvangen als slechts één lijn bezet is. Als u wisselgesprek uitschakelt, dan wordt een binnekomend gesprek automatisch geweigerd als één lijn bezet is. Hang up &both lines when ending a 3-way conference call. Hang &beide lijnen op bij het beëindigen van een 3-weg conferentiegesprek. Alt+B Alt+B Hang up both lines when you press bye to end a 3-way conference call. When this option is disabled, only the active line will be hung up and you can continue talking with the party on the other line. Hang beide lijnen op als u een 3-weg gesprek beëindigd. Als u deze optie uitschakelt, dan zal alleen de actieve lijn worden opgehangen. U kunt dan doorpraten met de partij op de andere lijn. &Maximum calls in call history: &Maximum aantal gesrpekken in gesprekshistorie: The maximum number of calls that will be kept in the call history. Het maximum aantal gesprekken dat in de gesprekshistorie wordt bewaard. &Auto show main window on incoming call after Toon hoofdvenster &automatisch bij een inkomend gesprek na Alt+A Alt+A When the main window is hidden, it will be automatically shown on an incoming call after the number of specified seconds. Als het hoofdvenster verborgen is, dan wordt deze automatisch getoond bij een inkomend gesprek na het aantal aangegeven seconden. Number of seconds after which the main window should be shown. Het aantal seconden waarna het hoofdvenster getoond moet worden. secs s The UDP port used for sending and receiving SIP messages. De UDP poort voor het sturen en ontvangen van SIP berichten. &RTP port: &RTP poort: The UDP port used for sending and receiving RTP for the first line. The UDP port for the second line is 2 higher. E.g. if port 8000 is used for the first line, then the second line uses port 8002. When you use call transfer then the next even port (eg. 8004) is also used. De UDP poort wordt gebruikt voor het sturen en ontvangen van RTP op de eerste lijn. De UDP poort voor de tweede lijn is 2 hoger. Voorbeeld: als poort 8000 wordt gebruikt voor de eerste lijn, dan wordt 8002 voor de tweede lijn gebruikt. Als u de dienst "doorverbinden" gebruikt dan wordt de volgende even poort (bijv. 8004) ook gebruikt. &SIP UDP port: &SIP UDP poort: Ring tone Ring tone &Play ring tone on incoming call &Speel ring tone bij inkomend gesprek Alt+P Alt+S Indicates if a ring tone should be played when a call comes in. Geeft aan dat een ring tone gespeeld moet worden bij een inkomend gesprek. &Default ring tone &Default reing tone Play the default ring tone when a call comes in. Speel de default ring tone bij een inkomend gesprek. C&ustom ring tone &Eigen ring tone Alt+U Alt+E Play a custom ring tone when a call comes in. Speel een eigen ring tone bij een inkomend gesprek. Specify the file name of a .wav file that you want to be played as ring tone. De bestandsnaam van een .wav bestand dat u als ring tone wilt afspelen. Ring back tone Ring back tone P&lay ring back tone when network does not play ring back tone S&peel ring back tone als het netwerk geen ring back tone speelt Alt+L Alt+P <p> Play ring back tone while you are waiting for the far-end to answer your call. </p> <p> Depending on your SIP provider the network might provide ring back tone or an announcement. </p> <p> Speel een ring back tone (de toon die u hoort als u iemand belt) als u wacht op antwoord van de gebelde partij. </p> <p> Afhankelijk van uw SIP provider, kan het netwerk een ring back tone spelen. </p> D&efault ring back tone De&fault ring back tone Play the default ring back tone. Speel de default ring back tone. Cu&stom ring back tone Ei&gen ring back tone Play a custom ring back tone. Speel een eigen ring back tone. Specify the file name of a .wav file that you want to be played as ring back tone. De bestandsnaam van een .wav bestand dat u als ring back tone wilt afspelen. &Lookup name for incoming call Naam op&zoeken voor een inkomend gesprek Ove&rride received display name Gevonden naam heeft voor&rang op ontvangen naam Alt+R Alt+R The caller may have provided a display name already. Tick this box if you want to override that name with the name you have in your address book. De beller kan een naam meesturen met een inkomend gesprek. Als u deze optie selecteert, dan zal Twinkle toch de naam uit uw adresboek tonen als deze gevonden wordt. Lookup &photo for incoming call Opzoeken &foto bij inkomend gesprek Lookup the photo of a caller in your address book and display it on an incoming call. Zoek de foto van de beller op in uw adresboek en toon het bij een inkomend gesprek. &OK &OK Alt+O Alt+O Accept and save your changes. Sla de instellingen op. &Cancel Ann&uleren Alt+C Alt+U Undo all your changes and close the window. Maak alle wijzigingen ongedaan en sluit het venster. none This is the 'none' in default IP address combo geen none This is the 'none' in default network interface combo geen Either choose a default IP address or a default network interface. Kies óf een default IP adres óf een default netwerk interface. Ring tones Description of .wav files in file dialog Ring tones Choose ring tone Kies ring tone Ring back tones Description of .wav files in file dialog Ring back tones Choose ring back tone Kies ring back tone &Validate devices before usage &Valideer apparaten voor gebruik Alt+V Alt+V <p> Twinkle validates the audio devices before usage to avoid an established call without an audio channel. <p> On startup of Twinkle a warning is given if an audio device is inaccessible. <p> If before making a call, the microphone or speaker appears to be invalid, a warning is given and no call can be made. <p> If before answering a call, the microphone or speaker appears to be invalid, a warning is given and the call will not be answered. <p> Twinkle valideert de geluidsapparaten voor gebruik om te voorkomen dat een gesprek zonder geluidskanaal wordt opgezet. <p> Bij het opstarten geeft Twinkle een waarschuwing als een geluidsapparaat niet beschikbaar is. <p> Als voor het maken van een gesprek, de microfoon of speaker niet beschikbaar zijn, dan krijgt u een waarschuwing en kunt u het gesprek niet maken. <p> Als voor het beantwoorden van een inkomend gesprek, de microfoon of speaker onbeschikbaar zijn, dan krijgt u een waarschuwing en kunt u het gesprek niet beantwoorden. On an incoming call, Twinkle will try to find the name belonging to the incoming SIP address in your address book. This name will be displayed. Bij een inkomend gesprek, probeert Twinkle de naam van de beller op te zoeken in het adresboek. Deze naam wordt dan getoond. Select ring tone file. Selecteer ring tone bestand. Select ring back tone file. Selecteer ring back tone bestand. Maximum allowed size (0-65535) in bytes of an incoming SIP message over UDP. Maximum omvang (0-65535) van een inkomend SIP bericht over UDP in bytes. &SIP port: &SIP poort: Max. SIP message size (&TCP): Max. omvang SIP bericht (&TCP): The UDP/TCP port used for sending and receiving SIP messages. De TCP/UDP poort die wordt gebruikt voor SIP verkeer. Max. SIP message size (&UDP): Max. omvang SIP bericht (&UDP): Maximum allowed size (0-4294967295) in bytes of an incoming SIP message over TCP. Maxmimum grootte (0-4294967295) van een SIP bericht over TCP in bytes. W&eb browser command: Command voor opstarten w&eb browser: Command to start your web browser. If you leave this field empty Twinkle will try to figure out your default web browser. Het commando waamee uw web browser kan worden opgestart. Als u dit veld leeg laat, dan zal Twinkle zelf proberen om uw standaard web browser op te starten. 512 512 1024 1024 Tip: for crackling sound with PulseAudio, set play period size to maximum. Enable in-call OSD SysTrayPopup Answer Antwoord Reject Afwijzen Incoming Call Inkomend gesprek TermCapForm Twinkle - Terminal Capabilities Twinkle - Terminal Eigenschappen &From: &Van: Get terminal capabilities of Opvragen terminal eigenschappen van &To: &Aan: The address that you want to query for capabilities (OPTION request). This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Het adres waarvoor u de eigenschappen (OPTION verzoek) wilt weten. Dit kan een volledig SIP adres zijn zoals <b>sip:example@example.com</b> of alleen een gebruikersnaam of telefoonnummer. Als u geen volledig adres invoert, dan zal Twinkle het adres afmaken met de waarde die u voor domein heeft ingevuld in uw gebruikersprofiel. Address book Adresboek Select an address from the address book. Kies een adres uit het adresboek. &OK &OK &Cancel Ann&uleren F10 F10 TransferForm Twinkle - Transfer Twinkle - Doorverbinden Transfer call to Doorverbinden naar &To: &Aan: The address of the person you want to transfer the call to. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Het adres waarnaar u wilt doorverbinden. Dit kan een volledig SIP adres zijn zoals <b>sip:example@example.com</b> of alleen een gebruikersnaam of telefoonnummer. Als u geen volledig adres invoert, dan zal Twinkle het adres afmaken met de waarde die u voor domein heeft ingevuld in uw gebruikersprofiel. Address book Adresboek Select an address from the address book. Kies een adres uit het adresboek. &OK &OK Alt+O Alt+O &Cancel Ann&uleren Type of transfer Doorverbindmethode &Blind transfer &Blind doorverbinden Alt+B Alt+B Transfer the call to a third party without contacting that third party yourself. Doorverbinden van het gesprek naar een derde partij zonder dat u die derde partij eerst zelf raadpleegt. T&ransfer with consultation Doorverbinden met &ruggespraak Alt+R Alt+R Before transferring the call to a third party, first consult the party yourself. Alvorens een gesprek door te verbinden naar een derde partij, houdt u eerst ruggespraak met die partij. Transfer to other &line Doorverbinden naar andere &lijn Alt+L Alt+L Connect the remote party on the active line with the remote party on the other line. Verbind de persoon op deze lijn door met de persoon op de andere lijn. F10 F10 TwinkleCore Directory %1 does not exist. Folder %1 bestaat niet. Lock file %1 already exist, but cannot be opened. Lock bestand %1 bestaat al, maar kan niet geopend worden. %1 is already running. Lock file %2 already exists. %1 is al actief. Lock bestand %2 bestaat al. Failed to create log file %1 . Aanmaken van log bestand %1 mislukt. Cannot open file for reading: %1 Bestand kan niet geopend worden om te lezen: %1 File system error while reading file %1 . Bestandssysteem fout tijdens lezen van file %1 . Cannot open file for writing: %1 Bestand niet geopend worden om te schrijven: %1 File system error while writing file %1 . Bestandssysteem fout tijdens schrijven van bestand %1 . Excessive number of socket errors. Excessief aantal socket fouten. Built with support for: Gebouwd met ondersteuning voor: Contributions: Bijdragen: This software contains the following software from 3rd parties: Deze software bevat de volgende software van derden: * GSM codec from Jutta Degener and Carsten Bormann, University of Berlin * GSM codec van Jutta Degener en Carsten Bormann, Universiteit van Berlijn * G.711/G.726 codecs from Sun Microsystems (public domain) * G.711/G.726 codecs van Sun Microsystems (public domain) * iLBC implementation from RFC 3951 (www.ilbcfreeware.org) * iLBC implementatie uit RFC 3951 (www.ilbcfreeware.org) * Parts of the STUN project at http://sourceforge.net/projects/stun * Delen van het STUN project op http://sourceforge.net/projects/stun * Parts of libsrv at http://libsrv.sourceforge.net/ * Delen van libsrv op http://libsrv.sourceforge.net/ For RTP the following dynamic libraries are linked: Voor RTP zijn de volgende dynamische libraries gelinkt: Translated to english by <your name> Nederlandse vertaling door Michel de Boer Cannot open file %1 . Betand %1 kan niet geopend worden. %1 is not set to your home directory. %1 heeft niet de waarde van uw thuis folder. Directory %1 (%2) does not exist. Folder %1 (%2) bestaat niet. Cannot create directory %1 . Folder %1 kan niet aangemaakt worden. Cannot create %1 . %1 kan niet aangemaakt worden. Cannot write to %1 . Kan niet schrijven naar %1 . Syntax error in file %1 . Syntactische fout in bestand %1 . Failed to backup %1 to %2 Backuppen van %1 naar %2 mislukt unknown name (device is busy) naam onbekend (apparaat bezet) Default device Default apparaat Anonymous Anoniem Warning: Waarschuwing: Call transfer - %1 Doorverbinden - %1 Sound card cannot be set to full duplex. Geluidskaart werkt niet in full duplex modus. Cannot set buffer size on sound card. De buffergrootte van de geluidskaart kan niet ingesteld worden. Sound card cannot be set to %1 channels. Geluidskaar ondersteunt geen %1 kanalen. Cannot set sound card to 16 bits recording. Geluidskaart ondersteunt geen 16 bits opname. Cannot set sound card to 16 bits playing. Geluidskaar ondersteunt geen 16 bits afspelen. Cannot set sound card sample rate to %1 Geluidskaar ondersteunt sampling rate %1 niet Opening ALSA driver failed Openen ALSA stuurprogramma mislukt Cannot open ALSA driver for PCM playback ALSA stuur programma voor PCM afspelen kan niet geopend worden Cannot resolve STUN server: %1 IP adres van STUN server niet gevonden: %1 You are behind a symmetric NAT. STUN will not work. Configure a public IP address in the user profile and create the following static bindings (UDP) in your NAT. U bevindt zich achter een symmetrische NAT. STUN zal niet werken. Configureer uw publieke IP adres in uw gebruikersprofiel en creëer de volgende statische UDP mapping in uw NAT. public IP: %1 --> private IP: %2 (SIP signaling) publiek IP: %1 --> privé IP: %2 (SIP signalering) public IP: %1-%2 --> private IP: %3-%4 (RTP/RTCP) publiek IP: %1-%2 --> privé IP: %3-%4 (RTP/RTCP) Cannot reach the STUN server: %1 STUN server onbereikbaar: %1 Port %1 (SIP signaling) Poort %1 (SIP signalering) NAT type discovery via STUN failed. Verkenning van NAT type via STUN is mislukt. If you are behind a firewall then you need to open the following UDP ports. Als u zich achter een firewall bevindt dan moet u de volgende UDP poorten open zetten. Ports %1-%2 (RTP/RTCP) Poorten %1-%2 (RTP/RTCP) Cannot access the ring tone device (%1). Ring tone apparaat niet beschikbaar (%1). Cannot access the speaker (%1). Speaker niet beschikbaar (%1). Cannot access the microphone (%1). Microfoon niet beschikbaar (%1). Cannot open ALSA driver for PCM capture ALSA stuurapparaat kan niet geopend worden voor openemen Cannot receive incoming TCP connections. Kan geen inkomende TCP verbindingen ontvangen. Failed to create file %1 Creëren van bestand %1 mislukt Failed to write data to file %1 Schrijven naar bestand %1 mislukt Failed to send message. Zenden bericht mislukt. Cannot lock %1 . Kan geen lock zetten op %1 . UserProfileForm Twinkle - User Profile Twinkle - Gebruikersprofiel User profile: Gebruikersprofiel: Select which profile you want to edit. Kies het gebruikersprofiel dat u wilt bewerken. User Gebruiker SIP server SIP server RTP audio RTP audio SIP protocol SIP protocol NAT NAT Address format Adresformaat Timers Timers Ring tones Ring tones Scripts Scripts Security Beveiliging Select a category for which you want to see or modify the settings. Kies de categorie die u wilt bewerken. &OK &OK Alt+O Alt+O Accept and save your changes. Bewaar uw wijzigingen. &Cancel Ann&uleren Alt+C Alt+U Undo all your changes and close the window. Maak alle wijzigingen ongedaan en sluit het venster. SIP account SIP account &User name*: Ge&bruikersnaam*: &Domain*: &Domein*: Or&ganization: Or&ganisatie: The SIP user name given to you by your provider. It is the user part in your SIP address, <b>username</b>@domain.com This could be a telephone number. <br><br> This field is mandatory. De SIP gebruikersnaam die u van uw provider heeft gekregen. Dit het gebruikersdeel in uw SIP adres, <b>gebruikersnaame</b>@domein.nl Dit kan een telefoonnummer zijn. <br><br> Dit is een verplicht veld. The domain part of your SIP address, username@<b>domain.com</b>. Instead of a real domain this could also be the hostname or IP address of your <b>SIP proxy</b>. If you want direct IP phone to IP phone communications then you fill in the hostname or IP address of your computer. <br><br> This field is mandatory. Dit is het domein deel van uw SIP adres, gebruikersnaame@<b>domein.nl</b>. In plaats van een echt domein kan dit ook de host naam of het IP van uw <b>SIP proxy</b> zijn. Als u direct van IP adres naar IP adres wilt bellen, dan vult u hier de host naam of IP adres van uw computer in. <br><br> Dit is een verplicht veld. You may fill in the name of your organization. When you make a call, this might be shown to the called party. Hier kunt u de naam van uw organisatie invullen. Als u iemand belt, dan kan dit getoond worden. This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. Dit is uw eigen naam. bijv. Jan Jansen. Als u iemand belt, kan deze naam getoond worden. &Your name: U&w naam: SIP authentication SIP authenticatie &Realm: &Realm: &Password: &Paswoord: The realm for authentication. This value must be provided by your SIP provider. If you leave this field empty, then Twinkle will try the user name and password for any realm that it will be challenged with. De authenticatie realm. Deze waarde moet verstrekt worden door uw SIP provider. Als u dit veld leeg laat, dan zal Twinkle automatisch de realm gebruiken die de SIP proxy stuurt. Als u de realm niet weet, laat dit veld dan leeg. Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Uw SIP gebruikersnaam voor authenticatie. Meestal is dit hetzelfde als uw SIP gebruikersnaam. Your password for authentication. Uw paswoord voor authenticatie. Registrar Registratie server &Registrar: &Registratie server: The hostname, domain name or IP address of your registrar. If you use an outbound proxy that is the same as your registrar, then you may leave this field empty and only fill in the address of the outbound proxy. De host naam, domein naam of IP adres van uw registratie server. Als u een uitgaande proxy gebruikt die tevens uw registratie server is, dan kunt u dit veld leeg laten en alleen het adres voor de uitgaande proxy invullen. &Expiry: &Interval: The registration expiry time that Twinkle will request. Het registratie interval dat Twinkle zal aanvragen. seconds sec Re&gister at startup Re&gistreer tijdens opstarten Alt+G Alt+G Indicates if Twinkle should automatically register when you run this user profile. You should disable this when you want to do direct IP phone to IP phone communication without a SIP proxy. Geeft aan of Twinkle zich automatisch moet registrerent als u dit gebruikersprofiel start. U moet deze optie uitschakelen als u direct van IP adres naar IP adres wilt bellen zonder tussenkomst van een SIP proxy. Outbound Proxy Uitgaande Proxy &Use outbound proxy &Gebruik uitgaande proxy Alt+U Alt+G Indicates if Twinkle should use an outbound proxy. If an outbound proxy is used then all SIP requests are sent to this proxy. Without an outbound proxy, Twinkle will try to resolve the SIP address that you type for a call invitation for example to an IP address and send the SIP request there. Geeft aan dat Twinkle een uitgaande proxy moet gebruiken. Als een uitgaande proxy gebruikt wordt, dan worden alle SIP berichten naar die proxy gestuurd. Zonder uitgaande proxy, zal Twinkle zelf proberen om een SIP adres naar een IP adres te veralen. Outbound &proxy: Uitgaande &proxy: &Send in-dialog requests to proxy &Stuur "in-dialog SIP request" naar de proxy Alt+S Alt+S SIP requests within a SIP dialog are normally sent to the address in the contact-headers exchanged during call setup. If you tick this box, that address is ignored and in-dialog request are also sent to the outbound proxy. SIP verzoeken binnen een SIP dialog worden normaliter naar het adres uit de contact-header gestuurd. Dit adres wordt tijdens de gespreksopbouw bepaald. Als u deze optie aanvinkt, dan zal Twinkle dat adres negeren en zullen in-dialog requests ook naar de uitgaande proxy gestuurd worden. &Don't send a request to proxy if its destination can be resolved locally. Stuur een &verzoek niet naar de proxy als de bestemming lokaal bepaald kan worden. Alt+D Alt+V When you tick this option Twinkle will first try to resolve a SIP address to an IP address itself. If it can, then the SIP request will be sent there. Only when it cannot resolve the address, it will send the SIP request to the proxy (note that an in-dialog request will only be sent to the proxy in this case when you also ticked the previous option.) Als u deze optie aanvinkt, dan zal Twinkle eerst zelf proberen om een SIP adres naar een IP adres te vertalen. Als dat lukt, dan zal het SIP verzoek naar dat IP adres worden gestuurd. Als dat niet lukt dan wordt het verzoek naar de proxy gestuurd (letop: als het om een in-dialog request gaat, dan moet ook de voorgaande optie aan staan, als u wilt dat deze naar de proxy gaat.) The hostname, domain name or IP address of your outbound proxy. Host naam, domein of IP adres van uw uitgaande proxy. Codecs Codecs Available codecs: Beschikbare codecs: G.711 A-law G.711 A-law G.711 u-law G.711 u-law GSM GSM speex-nb (8 kHz) speex-nb (8 kHz) speex-wb (16 kHz) speex-wb (16 kHz) speex-uwb (32 kHz) speex-uwb (32 kHz) List of available codecs. Lijst met beschikbare codecs. Move a codec from the list of available codecs to the list of active codecs. Verplaats een codec van de lijst met beschikbare codecs naar de lijst met actieve codecs. Move a codec from the list of active codecs to the list of available codecs. Verplaats een codec van de lijst met actieve codecs naar de lijst met beschikbare codecs. Active codecs: Actieve codecs: List of active codecs. These are the codecs that will be used for media negotiation during call setup. The order of the codecs is the order of preference of use. Lijst met actieve codecs. Deze codecs worden gebruikt in de media onderhandeling tijdens de gespreksopbouw. De volgorde van de codecs geeft de voorkeur aan. Move a codec upwards in the list of active codecs, i.e. increase its preference of use. Schuif een codec omhoog in de lijst met actieve codec, d.w.z. verhoog de voorkeur. Move a codec downwards in the list of active codecs, i.e. decrease its preference of use. Verschuif een codec omlaag in de lijst met actieve codecs, d.w.z. verlaag de voorkeur. &G.711/G.726 payload size: &G.711/G.726 payload grootte: The preferred payload size for the G.711 and G.726 codecs. De gewenste payload grootte voor de G.711 en G.726 codecs. ms ms &iLBC &iLBC iLBC iLBC i&LBC payload type: i&LBC payload type: iLBC &payload size (ms): iLBC &payload grootte (ms): The dynamic type value (96 or higher) to be used for iLBC. Het dynamische payload type (96 of hoger) voor iLBC. 20 20 30 30 The preferred payload size for iLBC. De gewenste payload grootte voor iLBC. &Speex &Speex Speex Speex Perceptual &enhancement Perceptual &enhancement Alt+E Alt+W Perceptual enhancement is a part of the decoder which, when turned on, tries to reduce (the perception of) the noise produced by the coding/decoding process. In most cases, perceptual enhancement make the sound further from the original objectively (if you use SNR), but in the end it still sounds better (subjective improvement). Perceptual enhancement tracht de ruis geproduceert door het coderings/decoderings proces te verminderen (een subjectieve verbetering). &Ultra wide band payload type: U&ltra wide band payload type: &VAD &VAD Alt+V Alt+V When enabled, voice activity detection detects whether the audio being encoded is speech or silence/background noise. VAD is always implicitly activated when encoding in VBR, so the option is only useful in non-VBR operation. In this case, Speex detects non-speech periods and encode them with just enough bits to reproduce the background noise. This is called "comfort noise generation" (CNG). Voice activity detection detecteert of de opgenomen audio spraak of stilte dan wel achtergrondruis is. VAD staat atlijd impliciet aan als VBR wordt gebruikt. VAD is dus alleen nutting als VBR uitstaat. De Speex codec stuurt dan slechts een paar bits tijdens stilte periodes. Dit heet comfort noise generation (CNG). &Wide band payload type: &Wide band payload type: V&BR V&BR Alt+B Alt+B Variable bit-rate (VBR) allows a codec to change its bit-rate dynamically to adapt to the "difficulty" of the audio being encoded. In the example of Speex, sounds like vowels and high-energy transients require a higher bit-rate to achieve good quality, while fricatives (e.g. s,f sounds) can be coded adequately with less bits. For this reason, VBR can achieve a lower bit-rate for the same quality, or a better quality for a certain bit-rate. Despite its advantages, VBR has two main drawbacks: first, by only specifying quality, there's no guarantee about the final average bit-rate. Second, for some real-time applications like voice over IP (VoIP), what counts is the maximum bit-rate, which must be low enough for the communication channel. Met variable bit-rate (VBR) past de codec de bit-rate dynamisch aan aan de complexiteit van de opgenomen audio. The dynamic type value (96 or higher) to be used for speex wide band. Het dynamische payload type (96 of hoger) voor speex wide band. Co&mplexity: Co&mplexiteit: DT&X DT&X Alt+X Alt+X Discontinuous transmission is an addition to VAD/VBR operation, that allows one to stop transmitting completely when the background noise is stationary. Discontinuous transmission (DTX) is een extra optie boven op VAD. Met DTX wordt helemaal niets gestuurd tijdens stilte periodes. The dynamic type value (96 or higher) to be used for speex narrow band. Het dynamische payload type (96 of hoger) voor speex narrow band. With Speex, it is possible to vary the complexity allowed for the encoder. This is done by controlling how the search is performed with an integer ranging from 1 to 10 in a way that's similar to the -1 to -9 options to gzip and bzip2 compression utilities. For normal use, the noise level at complexity 1 is between 1 and 2 dB higher than at complexity 10, but the CPU requirements for complexity 10 is about 5 times higher than for complexity 1. In practice, the best trade-off is between complexity 2 and 4, though higher settings are often useful when encoding non-speech sounds like DTMF tones. Voor Speex kan de complexiteit van de encoder ingesteld worden. Deze instelling is vergelijkbaar met de -1 tot -9 opties voor gzip en bzip2 compressie. Voor normaal gebruik is het ruisniveau bij complexiteit 1 tussen de 1 en 2 dB hoger dan bij complexiteit 10. De benodigde CPU kracht is bij complexiteit 10 ongeveer 5 keer zo hoog als bij complexiteit 1. In de praktijk is een complexiteit tussen 2 en 4 genoeg. Hogere complexiteit kan nuttig zijn bij het versturen van niet-spraak audio, bijvoorbeeld DTMF tonen. &Narrow band payload type: &Narrow band payload type: G.726 G.726 G.726 &40 kbps payload type: G.726 &40 kbps payload type: The dynamic type value (96 or higher) to be used for G.726 40 kbps. Het dynamische payload type (96 of hoger) voor G.726 40 kbps. The dynamic type value (96 or higher) to be used for G.726 32 kbps. Het dynamische payload type (96 of hoger) voor G.726 32 kbps. G.726 &24 kbps payload type: G.726 &24 kbps payload type: The dynamic type value (96 or higher) to be used for G.726 24 kbps. Het dynamische payload type (96 of hoger) voor G.726 24 kbps. G.726 &32 kbps payload type: G.726 &32 kbps payload type: The dynamic type value (96 or higher) to be used for G.726 16 kbps. Het dynamische payload type (96 of hoger) voor G.726 16 kbps. G.726 &16 kbps payload type: G.726 &16 kbps payload type: DT&MF DT&MF DTMF DTMF The dynamic type value (96 or higher) to be used for DTMF events (RFC 2833). Het dynamische payload type (96 of hoger) voor DTMF (RFC 2833). DTMF vo&lume: DTMF vo&lume: The power level of the DTMF tone in dB. Het volume van de DTMF toon in dB. The pause after a DTMF tone. Pauze na het sturen van een DTMF toon. DTMF &duration: DTMF &duur: DTMF payload &type: DTMF payload &type: DTMF &pause: DTMF &pauze: dB dB Duration of a DTMF tone. Duur van een DTMF toon. DTMF t&ransport: DTMF t&ransport: Auto Auto RFC 2833 RFC 2833 Inband Inband Out-of-band (SIP INFO) Out-of-band (SIP INFO) <h2>RFC 2833</h2> <p>Send DTMF tones as RFC 2833 telephone events.</p> <h2>Inband</h2> <p>Send DTMF inband.</p> <h2>Auto</h2> <p>If the far end of your call supports RFC 2833, then a DTMF tone will be send as RFC 2833 telephone event, otherwise it will be sent inband. </p> <h2>Out-of-band (SIP INFO)</h2> <p> Send DTMF out-of-band via a SIP INFO request. </p> <h2>RFC 2833</h2> <p>Stuur DTMF tonen als RFC 2833 events.</p> <h2>Inband</h2> <p>Stuur DTMF inband toon.</p> <h2>Auto</h2> <p>Als de andere partij RFC 2833 ondersteund dan wordt RFC 2833 gebruikt, anders wordt de DTMF toon inband gestuurd. </p> <h2>Out-of-band (SIP INFO)</h2> <p> Stuur DTMF out-of-band in een SIP INFO verzoek. </p> General Algemeen Redirection Doorverwijzen &Allow redirection St&a doorverwijzen toe Alt+A Alt+A Ask user &permission to redirect &Vraag toestemming voor doorverwijzen Alt+P Alt+V Indicates if Twinkle should ask the user before redirecting a request when a 3XX response is received. Geeft aan of Twinkle de gebruiker om toestemming moet vragen alvorens een verzoek door te verwijzen bij een 3XX antwoord. Max re&directions: Max &doorverwijzingen: The number of redirect addresses that Twinkle tries at a maximum before it gives up redirecting a request. This prevents a request from getting redirected forever. Het maximaal aantal doorverwijzingen dat Twinkle probeert om een verzoek af te leveren. Dit maximum voorkomt dat een verzoek eeuwig wordt doorverwezen. Protocol options Protocol opties Call &Hold variant: Wac&ht variant: RFC 2543 RFC 2543 RFC 3264 RFC 3264 Indicates if RFC 2543 (set media IP address in SDP to 0.0.0.0) or RFC 3264 (use direction attributes in SDP) is used to put a call on-hold. Geeft aan of RFC 2543 (zet media IP adres in SDP op 0.0.0.0) of RFC 3264 (gebruikt "direction" attribuut in SDP) gebruikt moet worden om een gesprek in de wacht te plaatsen. Allow m&issing Contact header in 200 OK on REGISTER Ontbrekende Contact header &in 200 OK op REGISTER toegestaan Alt+I Alt+I A 200 OK response on a REGISTER request must contain a Contact header. Some registrars however, do not include a Contact header or include a wrong Contact header. This option allows for such a deviation from the specs. Een 200 OK antwoord op een REGISTER verzoek moet een Contact header bevatten. Sommige registratie servers stoppen echter geen of een foutieve Contact header de 200 OK. Met deze optie staat u een dergelijke afwijking van de specificaties toe. &Max-Forwards header is mandatory &Max-Forwards header verplicht Alt+M Alt+M According to RFC 3261 the Max-Forwards header is mandatory. But many implementations do not send this header. If you tick this box, Twinkle will reject a SIP request if Max-Forwards is missing. Volgens RFC 3261 is de Max-Forwards header verplicht. Veel implementaties sturen deze header echter niet. Als u deze optie aanvinkt, dan zal Twinkle een SIP verzoek zonder Max-Forwards weigeren. Put &registration expiry time in contact header &Registratie interval in contact header Alt+R Alt+R In a REGISTER message the expiry time for registration can be put in the Contact header or in the Expires header. If you tick this box it will be put in the Contact header, otherwise it goes in the Expires header. In een REGISTER verzoek kan het registratie interval zowel in de Contact header als in de Expires header geplaatst worden. Als u deze optie aanvinkt, dan wordt het interval in de Contact header geplaatst, anders in de Expires header. &Use compact header names &Compacte header namen Indicates if compact header names should be used for headers that have a compact form. Geeft aan of compacte header namen gebruikt moeten worden voor headers die een compacte naam hebben. Allow SDP change during call setup SDP wijziging tijdens gespreksopbouw toestaan <p>A SIP UAS may send SDP in a 1XX response for early media, e.g. ringing tone. When the call is answered the SIP UAS should send the same SDP in the 200 OK response according to RFC 3261. Once SDP has been received, SDP in subsequent responses should be discarded.</p> <p>By allowing SDP to change during call setup, Twinkle will not discard SDP in subsequent responses and modify the media stream if the SDP is changed. When the SDP in a response is changed, it must have a new version number in the o= line.</p> <p>Een SIP UAS kan SDP in een 1XX antwoord sturen voor bijvoorbeeld een ring back tone. Als het gesprek beantwoord wordt, dan moet de SIP UAS dezelfde SDP in de 200 OK sturen volgens RFC 3261: <i>Once SDP has been received, SDP in subsequent responses should be discarded.</i></p> <p>Door een SDP wijziging toe te staan, zal Twinkle de SDP in de 200 OK niet negeren in dit geval en de media parameters aanpassen. Als de SDP wijzigt, dan moet die wel een nieuwe versienummer in de o= lijn hebben.</p> <p> Twinkle creates a unique contact header value by combining the SIP user name and domain: </p> <p> <tt>&nbsp;user_domain@local_ip</tt> </p> <p> This way 2 user profiles, having the same user name but different domain names, have unique contact addresses and hence can be activated simultaneously. </p> <p> Some proxies do not handle a contact header value like this. You can disable this option to get a contact header value like this: </p> <p> <tt>&nbsp;user@local_ip</tt> </p> <p> This format is what most SIP phones use. </p> <p> Twinkle maakt een unieke contact header waarde door de SIP gebruikersnaam te combineren met de domeinnaam: </p> <p> <tt>&nbsp;gebruikersnaam_domeinnaam@ip</tt> </p> <p> Zo krijgen 2 gebruikersprofielen met dezelfde gebruikersnaam, maar verschillende domeinnamen, toch een uniek contact adres. Hierdoor kunnen beide profielen tegelijkertijd geactiveerd worden. </p> <p> Sommige proxies vinden dit niet leuk. U kunt deze optie uitzetten voor een meer gangbare contact header: </p> <p> <tt>&nbsp;gebruikersnaam@ip</tt> </p> &Encode Via, Route, Record-Route as list Cod&eer Via, Route, Record-Route als lijst The Via, Route and Record-Route headers can be encoded as a list of comma separated values or as multiple occurrences of the same header. De Via, Route en Record-Route headers kunnen als een lijst met door komma's gescheiden waarden worden gecodeerd of als meerdere voorkomens van dezelfde header. SIP extensions SIP extensies &100 rel (PRACK): &100 rel (PRACK): disabled uitgeschakeld supported ondersteund required vereist preferred voorkeur Indicates if the 100rel extension (PRACK) is supported:<br><br> <b>disabled</b>: 100rel extension is disabled <br><br> <b>supported</b>: 100rel is supported (it is added in the supported header of an outgoing INVITE). A far-end can now require a PRACK on a 1xx response. <br><br> <b>required</b>: 100rel is required (it is put in the require header of an outgoing INVITE). If an incoming INVITE indicates that it supports 100rel, then Twinkle will require a PRACK when sending a 1xx response. A call will fail when the far-end does not support 100rel. <br><br> <b>preferred</b>: Similar to required, but if a call fails because the far-end indicates it does not support 100rel (420 response) then the call will be re-attempted without the 100rel requirement. Geeft aan of de 100rel extensie (PRACK) wordt ondersteund: <b>uitgeschakeld</b>: 100rel extensie is uitgeschakeld <br><br> <b>ondersteund</b>: 100rel wordt ondersteund (wordt toegevoegd aan de supported header in een INVITE). <br><br> <b>vereist</b>: 100rel is vereist (wordt toegevoegd aan de require header in een INVITE). Als een inkomende INVITE ondersteuning aangeeft voor 100rel, dan zal Twinkle een PRACK eisen bij het sturen van een 1XX antwoord. Een gesprek mislukt als de andere partij 100rel niet ondersteund. <br><br> <b>voorkeur</b>: Vergelijkbaar met <b>vereist</b>, maar als het gesprek mislukt omdat de andere partij 100rel niet ondersteund (420 antwoord), dan wordt het gesprek nogmaals opgezet zonder de 100rel eis. REFER REFER Call transfer (REFER) Doorverbinden (REFER) Allow call &transfer (incoming REFER) Doorverbinden &toestaan (inkomende REFER) Alt+T Alt+T Indicates if Twinkle should transfer a call if a REFER request is received. Geeft aan of Twinkle een gesprek moet doorverbinden als een REFER verzoek wordt ontvangen. As&k user permission to transfer &Vraag toestemming voor doorverbinden Alt+K Alt+V Indicates if Twinkle should ask the user before transferring a call when a REFER request is received. Geeft aan of Twinkle de gebruiker om toestemming moet vragen alvorens een gesprek door te verbinden als een REFER verzoek ontvangen wordt. Hold call &with referrer while setting up call to transfer target Zet de partij die u doorverbindt in de &wacht tijdens doorverbinden Alt+W Alt+W Indicates if Twinkle should put the current call on hold when a REFER request to transfer a call is received. Geeft aan of Twinkle het huidige gesprek in de wacht moet zetten als een REFER verzoek is ontvangen. Ho&ld call with referee before sending REFER &Zet andere partij in de wacht voor sturen REFER Alt+L Alt+Z Indicates if Twinkle should put the current call on hold when you transfer a call. Geeft aan of Twinkle de andere partij in de wacht moet zetten als u een gesprek doorverbindt. Auto re&fresh subscription to refer event while call transfer is not finished Automatisch verversen van re&fer subscriptie tijdens doorverbinden Alt+F Alt+F While a call is being transferred, the referee sends NOTIFY messages to the referrer about the progress of the transfer. These messages are only sent for a short interval which length is determined by the referee. If you tick this box, the referrer will automatically send a SUBSCRIBE to lengthen this interval if it is about to expire and the transfer has not yet been completed. Tijdens doorverbinden, stuurt de partij die wordt doorverbonden NOTIFY berichten naar de partij die doorverbindt. Deze NOTIFY berichten geven de voortgang van het doorverbinden aan. Deze berichten worden voor een bepaalde tijdsduur gestuurd. Als u deze optie aanvinkt, dan zal Twinkle automatisch een SUBSCRIBE verzoek sturen om de tijdsduur te verlengen als deze verloopt voordat het doorverbinden klaar is. NAT traversal NAT oplossing &NAT traversal not needed Geen &NAT Alt+N Alt+N Choose this option when there is no NAT device between you and your SIP proxy or when your SIP provider offers hosted NAT traversal. Kies deze optie als er geen router met netwerk adres translatie (NAT) zit tussen u en uw SIP proxy of als uw SIP provider "hosted NAT traversal" biedt. &Use statically configured public IP address inside SIP messages St&atisch publiek IP adres in SIP berichten Indicates if Twinkle should use the public IP address specified in the next field inside SIP message, i.e. in SIP headers and SDP body instead of the IP address of your network interface.<br><br> When you choose this option you have to create static address mappings in your NAT device as well. You have to map the RTP ports on the public IP address to the same ports on the private IP address of your PC. Geeft aan of Twinkle een statisch IP adres in de SIP berichten moet plaatsen, bijv. in headers en SDP in plaats van het prive IP adres van uw netwerk interface.<br><br> Als u deze optie kiest, dan moet u teven een adres vertaling in uw NAT router aanbrengen. U moet dezelfde RTP poorten op het publieke en prive adres gebruiken. Use &STUN &STUN Choose this option when your SIP provider offers a STUN server for NAT traversal. Kies deze optie als uw SIP provider een STUN server aanbiedt. S&TUN server: S&TUN server: The hostname, domain name or IP address of the STUN server. Host naam, domeinnaam of IP adres van de STUN server. &Public IP address: &Publiek IP adres: The public IP address of your NAT. Het publieke IP adres van uw NAT router. Telephone numbers Telefoonnummers Only &display user part of URI for telephone number Toon alleen gebruikers&deel van een URI voor een telnr If a URI indicates a telephone number, then only display the user part. E.g. if a call comes in from sip:123456@twinklephone.com then display only "123456" to the user. A URI indicates a telephone number if it contains the "user=phone" parameter or when it has a numerical user part and you ticked the next option. Als een URI een telefoonnummer is, toon dan alleen het gebruikersdeel. Voorbeeld: een gesprek komt binnen van sip:123456@twinklephone.com, dan wordt alleen "123456" getoond. Een URI is een telefoonnummer als het de parameter "user=phone" bevat of als het gebruikersdeel numeriek is en u vinkt de volgende optie aan. &URI with numerical user part is a telephone number URI met &numeriek gebruikersdeel is een telefoonnumer If you tick this option, then Twinkle considers a SIP address that has a user part that consists of digits, *, #, + and special symbols only as a telephone number. In an outgoing message, Twinkle will add the "user=phone" parameter to such a URI. Als u deze optie aanvinkt, dan ziet Twinkle een SIP adres dat bestaat uit cijfers, *, #, + en speciale symbolen als een telefoonnummer. In een uitgaand bericht, zal Twinkle dan de parameter "user=phone" toevoegen. &Remove special symbols from numerical dial strings Ve&rwijder speciale symbolen van numerieke strings Telephone numbers are often written with special symbols like dashes and brackets to make them readable to humans. When you dial such a number the special symbols must not be dialed. To allow you to simply copy/paste such a number into Twinkle, Twinkle can remove these symbols when you hit the dial button. Telefoonnummers worden vaak opgeschreven met speciale symbolen zoals streepjes voor de leesbaarheid. Als u een dergelijk nummer draait, dan moeten de speciale symbolen niet gedraaid worden. Om ervoor te zorgen dat u makkelijke telefoonnumers kan knippen en plakken naar Twinkle, kan Twinkle deze symbolen verwijderen als u op de "bel" knop drukt. &Special symbols: &Speciale symbolen: The special symbols that may be part of a telephone number for nice formatting, but must be removed when dialing. De speciale symbolen die in een telefoonnummer kunnen staan voor de leesbaarheid, maar die verwijderd moeten worden bij het bellen. Number conversion Nummer conversie Match expression Match expressie Replace Vervang <p> Often the format of the telphone numbers you need to dial is different from the format of the telephone numbers stored in your address book, e.g. your numbers start with a +-symbol followed by a country code, but your provider expects '00' instead of the '+', or you are at the office and all your numbers need to be prefixed with a '9' to access an outside line. Here you can specify number format conversion using Perl style regular expressions and format strings. </p> <p> For each number you dial, Twinkle will try to find a match in the list of match expressions. For the first match it finds, the number will be replaced with the format string. If no match is found, the number stays unchanged. </p> <p> The number conversion rules are also applied to incoming calls, so the numbers are displayed in the format you want. </p> <h3>Example 1</h3> <p> Assume your country code is 31 and you have stored all numbers in your address book in full international number format, e.g. +318712345678. For dialling numbers in your own country you want to strip of the '+31' and replace it by a '0'. For dialling numbers abroad you just want to replace the '+' by '00'. </p> <p> The following rules will do the trick: </p> <blockquote> <tt> Match expression = \+31([0-9]*) , Replace = 0$1<br> Match expression = \+([0-9]*) , Replace = 00$1</br> </tt> </blockquote> <h3>Example 2</h3> <p> You are at work and all telephone numbers starting with a 0 should be prefixed with a 9 for an outside line. </p> <blockquote> <tt> Match expression = 0[0-9]* , Replace = 9$&<br> </tt> </blockquote> <p> Vaak is het formaat van een telefoonnummer dat u moet draaien anders dan het formaat van het nummer in uw adresboek, bijv. uw nummers starten met een +-teken gevolgd door een landencode, maar uw provider verwacht '00' in plaats van het +-teken, of u bent op kantoor en u moet eerst een '9' draaien om naar buiten te bellen. Hier kunt u nummerformaatconversie definieren m.b.v. reguliere expressies en vervang strings (Perl syntax). </p> <p> Voor elk nummer dat u draait probeer Twinkle een match te vinden in de lijst met match expressies. Voor de eerste match die gevonden wordt, wordt het nummer vervangen door de vervang string. Als er geen match is, dan blijft het nummer onveranderd. </p> <p> Nummerconversie wordt ook toegepast op inkomende gesprekken, zodat de nummers getoond worden zoals u dat wilt. </p> <h3>Voorbeeld 1</h3> <p> Uw landencode is 31 en u heeft alle nummers in uw adresboek in volledig internationaal formaat opgeslagen, bijv. +318712345678. Om nummers binnen Nederland te draaien wilt u de '+31' vervangen door '0'. Voor het draaien van buitenlandse nummers wilt u de '+' vervangen door '00'. </p> <p> De volgende match/vervang regels doen dit voor u: </p> <blockquote> <tt> Match expressie = \+31([0-9]*) , Vervang = 0$1<br> Match expressie = \+([0-9]*) , Vervang = 00$1</br> </tt> </blockquote> <h3>Voorbeeld 2</h3> <p> U bent op kantoor en alle nummers met een 0 moeten voorafgegaan worden door een 9 voor een buitenlijn. </p> <blockquote> <tt> Match expressie = 0[0-9]* , Vervang = 9$&<br> </tt> </blockquote> Move the selected number conversion rule upwards in the list. Schuif de geselecteerde conversie omhoog in de lijst. Move the selected number conversion rule downwards in the list. Schuif de geselecteerde conversie omlaag in de lijst. &Add To&evoegen Add a number conversion rule. Voeg een nummerconversie toe. Re&move &Verwijder Remove the selected number conversion rule. Verwijder de geselcteerde nummerconversie. &Edit &Bewerk Edit the selected number conversion rule. Bewerk de geselecteerde nummerconversie. Type a telephone number here an press the Test button to see how it is converted by the list of number conversion rules. Voer een telefoonnummer in en druk op de Test-knop om te zien hoe het nummer geconverteerd wordt door de lijst van nummerconversies. &Test &Test Test how a number is converted by the number conversion rules. Test hoe een nummer geconverteerd wordt door de nummerconversies. for STUN STUN Keep alive timer for the STUN protocol. If you have enabled STUN, then Twinkle will send keep alive packets at this interval rate to keep the address bindings in your NAT device alive. Keep alive timer voor het STUN protocol. Als u STUN aan heeft gezet, dan zal Twinkle keep alive pakketjes sturen met deze snelheid om de adresbindingen in uw NAT router in leven te houden. When an incoming call is received, this timer is started. If the user answers the call, the timer is stopped. If the timer expires before the user answers the call, then Twinkle will reject the call with a "480 User Not Responding". Als een gesprek binnenkomt, dan wordt deze timer gestart. Als de gebruiker antwoordt, dan wordt de timer gestopt. Als de timer afloopt voordat de gebruiker antwoordt, dan weigert Twinkle het gesprek met "480 User Not Responding". NAT &keep alive: NAT &keep alive: &No answer: Gee&n antwoord: Ring &back tone: Ring &back tone: <p> Specify the file name of a .wav file that you want to be played as ring back tone for this user. </p> <p> This ring back tone overrides the ring back tone settings in the system settings. </p> <p> Naam van het .wav bestand dat u gespeeld wilt hebben als ring back tone voor dit gebruikersprofiel. </p> <p> Deze ring back tone heeft voorrang boven de ring back tone in de systeeminstellingen. </p> <p> Specify the file name of a .wav file that you want to be played as ring tone for this user. </p> <p> This ring tone overrides the ring tone settings in the system settings. </p> <p> Naam van het .wav bestand dat u gespeeld wilt hebben als ring tone voor dit gebruikersprofiel. </p> <p> Deze ring back tone heeft voorrang boven de ring tone in de systeeminstellingen. </p> &Ring tone: &Ring tone: <p> This script is called when you release a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing SIP BYE request are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=local_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the BYE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Dit script wordt aangeroepen als u een gesprek beëindigd. </p> <h2>Variabelen</h2> <p> De waarden van alle SIP headers van de uitgaande SIP BYE verzoek worden via variabelen aan uw script doorgegeven. </p> <p> <b>TWINKLE_TRIGGER=local_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> bevat request-URI van de BYE. <b>TWINKLE_USER_PROFILE</b> bevat de gebruikersprofielnaam. <p> This script is called when an incoming call fails. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing SIP failure response are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=in_call_failed</b>. <b>SIPSTATUS_CODE</b> contains the status code of the failure response. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Dit script wordt aangeroepen als een inkomend gesprek mislukt. </p> <h2>Variabelen</h2> De waarden van alle SIP headers van de uitgaande SIP fout indicatie worden via variabelen aan uw script doorgegeven. </p> <p> <b>TWINKLE_TRIGGER=in_call_failed</b>. <b>SIPSTATUS_CODE</b> bevat de status code de fout indicatie. <b>SIPSTATUS_REASON</b> bevat de foutmelding. <b>TWINKLE_USER_PROFILE</b> bevat de gebruikersprofielnaam. <p> This script is called when the remote party releases a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming SIP BYE request are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=remote_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the BYE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Dit script wordt aangeroepen als de andere partij het gesprek beëindigd. </p> <h2>Variabelen</h2> <p> De waarden van alle SIP headers van de inkomende SIP BYE worden via variabelen aan uw script doorgegeven. </p> <p> <b>TWINKLE_TRIGGER=remote_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> bevat de request-URI van de BYE. <b>TWINKLE_USER_PROFILE</b> bevat de gebruikersprofielnaam. <p> This script is called when the remote party answers your call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming 200 OK are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Dit script wordt aangeroepen als de andere partij uw gesprek beantwoordt. </p> <h2>Variabelen</h2> <p> De waarden van alle SIP headers van de inkomende 200 OK worden via variabelen aan uw script doorgegeven. </p> <b>TWINKLE_TRIGGER=out_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> bevat de status melding. <b>TWINKLE_USER_PROFILE</b> bevat de gebruikersprofielnaam. <p> This script is called when you answer an incoming call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing 200 OK are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=in_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Dit script wordt aangeroepen als u een inkomend gesprek beantwoordt. </p> <h2>Variabelen</h2> <p> De waarden van alle SIP headers van de uitgaande 200 OK worden via variabelen aan uw script doorgegeven. </p> <b>TWINKLE_TRIGGER=in_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> bevat de status melding. <b>TWINKLE_USER_PROFILE</b> bevat de gebruikersprofielnaam. Call released locall&y: Gesprek beëindigd &door uzelf: <p> This script is called when an outgoing call fails. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming SIP failure response are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call_failed</b>. <b>SIPSTATUS_CODE</b> contains the status code of the failure response. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Dit script wordt aangeroepen als een uitgaand gesprek mislukt. </p> <h2>Variabelen</h2> <p> De waarden van alle SIP headers van de inkomende SIP foutmelding worden via variabelen aan uw script doorgegeven. </p> <b>TWINKLE_TRIGGER=out_call_failed</b>. <b>SIPSTATUS_CODE</b> bevat de foutcode. <b>SIPSTATUS_REASON</b> bevat de foutmelding. <b>TWINKLE_USER_PROFILE</b> bevat de gebruikersprofielnaam. <p> This script is called when you make a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing INVITE are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call</b>. <b>SIPREQUEST_METHOD=INVITE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the INVITE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> Dit script wordt aangeroepen als u een nummer draait. </p> <h2>Variabelen</h2> <p> De waarden van alle SIP headers van de uitgaande SIP INVITE worden via variabelen aan uw script doorgegeven. </p> <p> <b>TWINKLE_TRIGGER=out_call</b>. <b>SIPREQUEST_METHOD=INVITE</b>. <b>SIPREQUEST_URI</b> bevat request-URI van de INVITE. <b>TWINKLE_USER_PROFILE</b> bevat de gebruikersprofielnaam. Outgoing call a&nswered: Uitgaand gesprek bea&ntwoord: Incoming call &failed: Inkomend gesprek &mislukt: &Incoming call: &Inkomend gesprek: Call released &remotely: Gesp&rek beëindigd door ander: Incoming call &answered: Inkomend gesprek be&antwoord: O&utgoing call: Ui&tgaand gesprek: Out&going call failed: Uit&gaand gesprek mislukt: &Enable ZRTP/SRTP encryption ZRTP/SRTP &encryptie When ZRTP/SRTP is enabled, then Twinkle will try to encrypt the audio of each call you originate or receive. Encryption will only succeed if the remote party has ZRTP/SRTP support enabled. If the remote party does not support ZRTP/SRTP, then the audio channel will stay unecrypted. Als u ZRTP/SRTP aanzet, dan zal Twinkle proberen om het audiokanaal van uw gesprekken te versleutelen. Versleuteling lukt alleen als uw gesprekspartner ook ZRTP/SRTP ondersteunt. Als uw gesprekspartner geen ZRTP/SRTP ondersteund, dan blijft het audiokanaal onversleuteld. ZRTP settings ZRTP instellingen O&nly encrypt audio if remote party indicated ZRTP support in SDP Allee&n versleutelen als de andere partij ZRTP ondersteuning in SDP aangeeft A SIP endpoint supporting ZRTP may indicate ZRTP support during call setup in its signalling. Enabling this option will cause Twinkle only to encrypt calls when the remote party indicates ZRTP support. Een SIP toestel kan tijdens de gespreksopbouw aangeven of het ZRTP ondersteunt. Met deze optie zal Twinkle de audio alleen proberen te versleutelen als de andere partij ondesteuning voor ZRTP gesignaleerd heeft. &Indicate ZRTP support in SDP S&ignaleer ZRTP ondersteuning in SDP Twinkle will indicate ZRTP support during call setup in its signalling. Twinkle zal tijdens gespreksopbouw aangeven dat het ZRTP ondersteunt. &Popup warning when remote party disables encryption during call &Popup waarschuwing als de andere partij versleuteling uitzet A remote party of an encrypted call may send a ZRTP go-clear command to stop encryption. When Twinkle receives this command it will popup a warning if this option is enabled. Als de andere partij tijdens een versleuteld gesprek een ZRTP go-clear commando stuurt om de versleuteling te stoppen, dan zal Twinkle een waarschuwing geven. Dynamic payload type %1 is used more than once. Dynamische payload type %1 is meer malen gebruikt. You must fill in a user name for your SIP account. U moet een gebruikersnaam voor uw SIP account invullen. You must fill in a domain name for your SIP account. This could be the hostname or IP address of your PC if you want direct PC to PC dialing. U moet een domeinnaam voor uw SIP account invullen. Dit kan de host naam of IP adres van uw PC zijn als u direct van PC naar PC wilt bellen. Invalid user name. Ongeldige gebruikersnaam. Invalid domain. Ongeldig domein. Invalid value for registrar. Ongeldige waarde voor de registratie server. Invalid value for outbound proxy. Ongeldige waarde voor de uitgaande proxy. Value for public IP address missing. Publiek IP adres ontbreekt. Invalid value for STUN server. Ongeldige waarde voor STUN server. Ring tones Description of .wav files in file dialog Ring tones Choose ring tone Kies ring tone Ring back tones Description of .wav files in file dialog Ring back tones All files Alle bestanden Choose incoming call script Kies script voor inkomende gesprekken Choose incoming call answered script Kies script voor beantwoording inkomend gesprek Choose incoming call failed script Kies script voor mislukken inkomend gesprek Choose outgoing call script Kies script voor uitgaande gesprekken Choose outgoing call answered script Kies script voor beantwoording uitgaande gesprek Choose outgoing call failed script Kies script voor mislukken uitgaande gesprek Choose local release script Kies script voor beëindigen gesprek door uzelf Choose remote release script Kies script voor beëindigen gesprek door ander Co&decs Co&decs Indicates if Twinkle should redirect a request if a 3XX response is received. Geeft aan of Twinkle een verzoek moet doorverwijzen als een 3XX antwoord wordt ontvangen. Authentication &name: Authenticatie &naam: Voice mail Voice mail &Follow codec preference from far end on incoming calls &Volg codec voorkeur van de beller bij inkomende gesprekken <p> For incoming calls, follow the preference from the far-end (SDP offer). Pick the first codec from the SDP offer that is also in the list of active codecs. <p> If you disable this option, then the first codec from the active codecs that is also in the SDP offer is picked. <p> Volg de voorkeur van de beller (SDP aanbod) bij inkomende gesprekken. Neem de eerste codec uit het SDP aanbod dat ook in de lijst van actieve codecs voorkomt. <p> Als u deze optie uitschakeld, dan neemt Twinkle de eerste codec uit de actieve codec lijst die ook in het SDP aanbod voorkomt. Follow codec &preference from far end on outgoing calls Volg &codec voorkeur van de gebelde bij uitgaande gesprekken <p> For outgoing calls, follow the preference from the far-end (SDP answer). Pick the first codec from the SDP answer that is also in the list of active codecs. <p> If you disable this option, then the first codec from the active codecs that is also in the SDP answer is picked. <p> Volg de voorkeur van de gebelde (SDP antwoord) bij uitgaande gesprekken. Neem de eerste codec uit het SDP antwoord dat ook in de lijst van actieve codecs voorkomt. <p> Als u deze optie uitschakelt, dan neemt Twinkle de eerste codec uit de actieve codec lijst die ook in het SDP antwoord voorkomt. Replaces Replaces Indicates if the Replaces-extenstion is supported. Geeft aan of de Replaces-extensie ondersteund wordt. Attended refer to AoR (Address of Record) Begeleid doorverbinden maar AoR (Address of Record) An attended call transfer should use the contact URI as a refer target. A contact URI may not be globally routable however. Alternatively the AoR (Address of Record) may be used. A disadvantage is that the AoR may route to multiple endpoints in case of forking whereas the contact URI routes to a single endoint. Bij begeleid doorverbinden, is de contact URI de doorverbindbestemming. Een contact URI kan echter niet globaal routeerbaar zijn. Als alternatief kan dan de AoR (Address of Record) gebruikt worden. Een nadeel van het gebruik van de AoR is dat deze routeerbaar kan zijn naar meerdere eindpunten in het geval van SIP forking. De contact URI routeert altijd naar een uniek eindpunt. Privacy Privacy Privacy options Privacy opties &Send P-Preferred-Identity header when hiding user identity &Stuur P-Preferred-Identity header bij anonieme gesprekken Include a P-Preferred-Identity header with your identity in an INVITE request for a call with identity hiding. Stuur de P-Preferred-Identity header in een INVITE verzoek, als u uw identiteit verbergt bij het maken van een gesprek. <p> You can customize the way Twinkle handles incoming calls. Twinkle can call a script when a call comes in. Based on the output of the script Twinkle accepts, rejects or redirects the call. When accepting the call, the ring tone can be customized by the script as well. The script can be any executable program. </p> <p> <b>Note:</b> Twinkle pauses while your script runs. It is recommended that your script does not take more than 200 ms. When you need more time, you can send the parameters followed by <b>end</b> and keep on running. Twinkle will continue when it receives the <b>end</b> parameter. </p> <p> With your script you can customize call handling by outputting one or more of the following parameters to stdout. Each parameter should be on a separate line. </p> <p> <blockquote> <tt> action=[ continue | reject | dnd | redirect | autoanswer ]<br> reason=&lt;string&gt;<br> contact=&lt;address to redirect to&gt;<br> caller_name=&lt;name of caller to display&gt;<br> ringtone=&lt;file name of .wav file&gt;<br> display_msg=&lt;message to show on display&gt;<br> end<br> </tt> </blockquote> </p> <h2>Parameters</h2> <h3>action</h3> <p> <b>continue</b> - continue call handling as usual<br> <b>reject</b> - reject call<br> <b>dnd</b> - deny call with do not disturb indication<br> <b>redirect</b> - redirect call to address specified by <b>contact</b><br> <b>autoanswer</b> - automatically answer a call<br> </p> <p> When the script does not write an action to stdout, then the default action is continue. </p> <p> <b>reason: </b> With the reason parameter you can set the reason string for reject or dnd. This might be shown to the far-end user. </p> <p> <b>caller_name: </b> This parameter will override the display name of the caller. </p> <p> <b>ringtone: </b> The ringtone parameter specifies the .wav file that will be played as ring tone when action is continue. </p> <h2>Environment variables</h2> <p> The values of all SIP headers in the incoming INVITE message are passed in environment variables to your script. The variable names are formatted as <b>SIP_&lt;HEADER_NAME&gt;</b> E.g. SIP_FROM contains the value of the from header. </p> <p> TWINKLE_TRIGGER=in_call. SIPREQUEST_METHOD=INVITE. The request-URI of the INVITE will be passed in <b>SIPREQUEST_URI</b>. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> U kunt het gedrag waarmee Twinkle inkomende gesprekken afhandelt aanpassen met een script dat Twinkle aanroept als een gesprek binnenkomt. Afhankelijk van de output van het script accepteert of weigert Twinkle het gesprek of verwijst het door. </p> <p> <b>Let op:</b> Twinkle staat stil als het script loopt. Het is aanbevolen dat uw script niet langer dan 200 ms loopt. Als u meer tijd nodig heeft, dan kunt u de parameters sturen gevolgd door <b>end</b>. Twinkle gaat verder zodra het <b>end</b> ontvangt, terwijl u script blijft draaien. </p> <p> U kunt Twinkle sturen door de volgende parameters naar stdout te schrijven. Elk op een nieuwe regel. </p> <p> <blockquote> <tt> action=[ continue | reject | dnd | redirect | autoanswer ]<br> reason=&lt;string&gt;<br> contact=&lt;adres voor doorverwijzen&gt;<br> caller_name=&lt;toon deze naam&gt;<br> ringtone=&lt;naam van .wav bestand&gt;<br> display_msg=&lt;toon bericht op scherm&gt;<br> end<br> </tt> </blockquote> </p> <h2>Parameters</h2> <h3>action</h3> <p> <b>continue</b> - handel gesprek af op normale wijze<br> <b>reject</b> - weiger gesprek<br> <b>dnd</b> - weiger gesprek met niet-storen indicatie<br> <b>redirect</b> - verwijs gesprek door naar <b>contact</b><br> <b>autoanswer</b> - automatisch antwoorden<br> </p> <p> Als een script geen actie op stdout zet, dan is de actie "continue" </p> <p> <b>reason: </b> Met de reason parameter, zet u de SIP reason string voor reject of dnd. Dit kan getoond worden aan de gebruiker. </p> <p> <b>caller_name: </b> Toon deze naam in plaats van de display naam. </p> <p> <b>ringtone: </b> De ring tone die gespeeld moet worden als de actie "continue" is. </p> <h2>Variables</h2> <p> De waarden van alle SIP headers van de inkomende INVITE worden via variabelen aan uw script doorgegeven. De variabele namen zijn als volgt samengesteld <b>SIP_&lt;HEADER_NAME&gt;</b> Bijv. SIP_FROM bevat de waarde van de from header. </p> <p> TWINKLE_TRIGGER=in_call. SIPREQUEST_METHOD=INVITE. <b>SIPREQUEST_URI</b> bevat de request-URI van de INVITE. <b>TWINKLE_USER_PROFILE</b> bevat de gebruikersprofielnaam. &Voice mail address: &Voice mail adres: The SIP address or telephone number to access your voice mail. SIP adres of telefoonnummer van uw voice mail. Unsollicited Unsollicited Sollicited Sollicited <H2>Message waiting indication type</H2> <p> If your provider offers the message waiting indication service, then Twinkle can show you when new voice mail messages are waiting. Ask your provider which type of message waiting indication is offered. </p> <H3>Unsollicited</H3> <p> Asterisk provides unsollicited message waiting indication. </p> <H3>Sollicited</H3> <p> Sollicited message waiting indication as specified by RFC 3842. </p> <H2>Message waiting indication type</H2> <p> Als uw provider de dienst aanbiedt waarmee u uw voice mail status kunt zien, dan kan Twinkle laten zien hoeveel nieuwe voice mail berichten er op u wachten. Er zijn 2 methoden waarop deze dienst kan worden aangeboden. </p> <H3>Unsollicited</H3> <p> Asterisk biedt unsollicited message waiting indication. </p> <H3>Sollicited</H3> <p> Sollicited message waiting indication zoals gespecificeerd in RFC 3842. </p> &MWI type: &MWI type: Sollicited MWI Sollicited MWI Subscription &duration: Aanmeldings&duur: Mailbox &user name: Mailbox &gebruikersnaam: The hostname, domain name or IP address of your voice mailbox server. De host naam, domeinnaam of IP adres van uw voice mailbox server. For sollicited MWI, an endpoint subscribes to the message status for a limited duration. Just before the duration expires, the endpoint should refresh the subscription. Twinkle meldt zich voor een bepaalde periode aan bij de voice mailbox server. Net voordat deze periode verstrijkt, zal Twinkle zich opnieuw aanmelden. Your user name for accessing your voice mailbox. Uw gebruikersnaam voor toegang tot uw mailbox. Mailbox &server: Mailbox &server: Via outbound &proxy Via uitgaande &proxy Check this option if Twinkle should send SIP messages to the mailbox server via the outbound proxy. Vink deze optie aan als Twinkle de SIP berichten naar de mailbox server via de uitgaande proxy moet sturen. You must fill in a mailbox user name. U moet een mailbox gebruikersnaam invullen. You must fill in a mailbox server U moet een mailbox server invullen Invalid mailbox server. Ongeldige mailbox server. Invalid mailbox user name. Ongeldige mailbox gebruikersnaam. Codeword &packing order: Codeword &packing volgorde: RFC 3551 RFC 3551 ATM AAL2 ATM AAL2 There are 2 standards to pack the G.726 codewords into an RTP packet. RFC 3551 is the default packing method. Some SIP devices use ATM AAL2 however. If you experience bad quality using G.726 with RFC 3551 packing, then try ATM AAL2 packing. Er zijn 2 methoden om G.726 codewords in een RTP pakket te stoppen. RFC 3551 is de default methode. Sommige SIP apparaten gebruiken de ATM AAL2 methode echter. Als bij het gebruik van G.726 met RFC 3551 packing de geluidskwaliteit slecht is, probeer dan ATM AAL2 packing. Use domain &name to create a unique contact header value &Gebruik domeinnaam voor een unieke contact header Select ring back tone file. Selecteer ring back tone bestand. Select ring tone file. Selecteer ring tone bestand. Select script file. Selecteer script bestand. %1 converts to %2 %1 wordt geconverteerd naar %2 Instant message Instant bericht Presence Beschikbaarheid &Maximum number of sessions: &Maximum aantal sessies: When you have this number of instant message sessions open, new incoming message sessions will be rejected. Als u het maximum aantal berichtensessies actief heeft, dan zullen inkomende berichten voor nieuwe sessies geweigerd worden. Your presence Uw beschikbaarheid &Publish availability at startup &Publiceer beschikbaarheid bij opstarten Publish your availability at startup. Publiceer uw beschikbaarheid bij opstarten. Buddy presence Beschikbaarheid van vrienden Publication &refresh interval (sec): Publicatie &interval (sec): Refresh rate of presence publications. Verversingssnelheid van beschikbaarheidspublicaties. &Subscription refresh interval (sec): Aan&meldingsinterval (sec): Refresh rate of presence subscriptions. Verversingsnelheid van beschikbaarheidsaanmeldingen. Transport/NAT Transport/NAT Add q-value to registration Voeg q-waarde toe aan registratie The q-value indicates the priority of your registered device. If besides Twinkle you register other SIP devices for this account, then the network may use these values to determine which device to try first when delivering a call. De q-waarde is de prioriteit van een apparaat. Als u naast Twinkle nog andere SIP apparaten bij het netwerk registreert voor deze gebruiker, dan kan het netwerk deze waarde gebruiken om te bepalen op welk apparaat een gesprek als eerste afgeleverd moet worden. The q-value is a value between 0.000 and 1.000. A higher value means a higher priority. De q-waarde is een waarde tussen 0,000 en 1,000. Een hogere waarde betekent een hogere prioriteit. SIP transport SIP transport UDP UDP TCP TCP Transport mode for SIP. In auto mode, the size of a message determines which transport protocol is used. Messages larger than the UDP threshold are sent via TCP. Smaller messages are sent via UDP. Transport modus voor SIP. In auto modus bepaalt de berichtgrootte welk transport protocol gebruikt wordt. Berichten groter dan de UDP drempel worden via TCP verstuurd. Kleinere berichten worden via UDP gestuurd. T&ransport protocol: T&ransport protocol: UDP t&hreshold: UDP &drempel: bytes bytes Messages larger than the threshold are sent via TCP. Smaller messages are sent via UDP. Berichten groter dan de drempel worden via TCP verstuurd. Kleinere berichten worden via UDP verstuurd. Use &STUN (does not work for incoming TCP) &STUN (werkt niet voor inkomend TCP verkeer) P&ersistent TCP connection P&ersistente TCP verbinding Keep the TCP connection established during registration open such that the SIP proxy can reuse this connection to send incoming requests. Application ping packets are sent to test if the connection is still alive. De TCP verbinding die opgezet wordt tijdens registratie blijft open, zodat de SIP proxy deze verbinding kan gebruiken om binnenkomende verzoeken te sturen. Ping pakketten worden gestuurd om te testen of de verbinding nog bestaat. &Send composing indications when typing a message. &Zend indicaties als u een bericht aan het schrijven bent. Twinkle sends a composing indication when you type a message. This way the recipient can see that you are typing. Twinkle stuurt een compositie indicatie als u een bericht aan het schrijven bent. De ontvanger van uw bericht kan dan zien dat u bezig bent met schrijven. AKA AM&F: AkA AM&F: A&KA OP: A&KA OP: Authentication management field for AKAv1-MD5 authentication. "Authentication management field" voor AKAv1-MD5 authenticatie. Operator variant key for AKAv1-MD5 authentication. "Operator variant key" voor AKAv1-MD5 authenticatie. Prepr&ocessing V&oorbewerking Preprocessing (improves quality at remote end) Voorbewerking (verbetert de geluidskwaliteit voor uw gesprekspartner) &Automatic gain control &Automatische sterkteregeling Automatic gain control (AGC) is a feature that deals with the fact that the recording volume may vary by a large amount between different setups. The AGC provides a way to adjust a signal to a reference volume. This is useful because it removes the need for manual adjustment of the microphone gain. A secondary advantage is that by setting the microphone gain to a conservative (low) level, it is easier to avoid clipping. Automatische sterkteregeling versterkt zachte signalen en dempt luide signalen die door de microfoon worden opgenomen. Automatic gain control &level: Niveau automatische sterkterege&ling: Automatic gain control level represents percentual value of automatic gain setting of a microphone. Recommended value is about 25%. Een waarde rond 25% is aanbevolen voor een goede geluidskwaliteit. &Voice activity detection &Voice activity detection When enabled, voice activity detection detects whether the input signal represents a speech or a silence/background noise. "Voice activity detection" detecteteert of een signaal spraak of stilte/ruid bevat. Stilte/ruis wordt niet over het netwerk gestuurd waardoor minder bandbreedte gebruikt wordt. &Noise reduction Ruisonderdrukki&ng The noise reduction can be used to reduce the amount of background noise present in the input signal. This provides higher quality speech. Ruisonderdrukking vermindert achtergrondruis in het microfoonsignaal. Acoustic &Echo Cancellation Acoustic &Echo Cancellation In any VoIP communication, if a speech from the remote end is played in the local loudspeaker, then it propagates in the room and is captured by the microphone. If the audio captured from the microphone is sent directly to the remote end, then the remote user hears an echo of his voice. An acoustic echo cancellation is designed to remove the acoustic echo before it is sent to the remote end. It is important to understand that the echo canceller is meant to improve the quality on the remote end. Spraak uit de speakers wordt opgevangen door de microfoon waardoor uw gesprekspartner een echo waarneemt. "Acoustic echo cancellation" verwijdert deze echo. Variable &bit-rate Variable &bit-rate Discontinuous &Transmission Discontinuous &Transmission &Quality: &Kwaliteit: Speex is a lossy codec, which means that it achives compression at the expense of fidelity of the input speech signal. Unlike some other speech codecs, it is possible to control the tradeoff made between quality and bit-rate. The Speex encoding process is controlled most of the time by a quality parameter that ranges from 0 to 10. Speex comprimeert het geluidssignaal ten koste van de kwaliteit. Hoe meer compressie, hoe minder bandbreedte nodig is, maar hoe slechter de geluidskwaliteit. Deze kwaliteitsfactor (0 tot 10) bepaalt de trade-off tussen kwaliteit en compressie. bytes bytes Use tel-URI for telephone &number Vestuur telefoon&nummer als tel-URI Expand a dialed telephone number to a tel-URI instead of a sip-URI. Expandeer een telefoonnummer naar een tel-URI in plaats van een sip-URI. Accept call &transfer request (incoming REFER) Accep&teer doorverbindverzoek (inkomende REFER) Allow call transfer while consultation in progress Doorverbinden toestaan tijdens opbouw ruggespraak gesprek When you perform an attended call transfer, you normally transfer the call after you established a consultation call. If you enable this option you can transfer the call while the consultation call is still in progress. This is a non-standard implementation and may not work with all SIP devices. Bij doorverbinden met ruggespraak, verbindt u normaal pas door nadat u ruggespraak heeft gehouden. Met deze optie kunt u al doorverbinden terwijl het gesprek voor ruggespraak nog in opbouw is. Dit is een niet-standaard implementatie die mogelijk niet werkt met alle SIP apparaten. Enable NAT &keep alive NAT &keep alive Send UDP NAT keep alive packets. Stuur UDP NAT keep alive pakketten. If you have enabled STUN or NAT keep alive, then Twinkle will send keep alive packets at this interval rate to keep the address bindings in your NAT device alive. Als u STUN of NAT keep alive aan heeft gezet, dan zal Twinkle keep alive pakketjes sturen met deze snelheid om de adresbindingen in uw NAT router in leven te houden. WizardForm Twinkle - Wizard Twinkle - Wizard The hostname, domain name or IP address of the STUN server. De host naam, domeinnaam of IP adres van de STUN server. S&TUN server: S&TUN server: The SIP user name given to you by your provider. It is the user part in your SIP address, <b>username</b>@domain.com This could be a telephone number. <br><br> This field is mandatory. De SIP gebruikersnaam die u van uw provider heeft gekregen. Dit het gebruikersdeel in uw SIP adres, <b>gebruikersnaame</b>@domein.nl Dit kan een telefoonnummer zijn. <br><br> Dit is een verplicht veld. &Domain*: &Domein*: Choose your SIP service provider. If your SIP service provider is not in the list, then select <b>Other</b> and fill in the settings you received from your provider.<br><br> If you select one of the predefined SIP service providers then you only have to fill in your name, user name, authentication name and password. Kies uw SIP provider. Als uw SIP provider niet in de lijst voorkomt, kies dan <b>Anders</b> en vul de instellingen in die uw van uw provider hebt gekregen.<br><br> Als u een voorgedefinieerde SIP provider kiest, dan hoeft u alleen uw eigen naam, gebruikersnaam, authenticatie naam en paswoord in te voeren. &Authentication name: &Authenticatie naam: &Your name: U&w naam: Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Uw SIP gebruikersnaam voor authenticatie. Meestal is dit hetzelfde als uw SIP gebruikersnaam. The domain part of your SIP address, username@<b>domain.com</b>. Instead of a real domain this could also be the hostname or IP address of your <b>SIP proxy</b>. If you want direct IP phone to IP phone communications then you fill in the hostname or IP address of your computer. <br><br> This field is mandatory. Dit is het domein deel van uw SIP adres, gebruikersnaame@<b>domein.nl</b>. In plaats van een echt domein kan dit ook de host naam of het IP van uw <b>SIP proxy</b> zijn. Als u direct van IP adres naar IP adres wilt bellen, dan vult u hier de host naam of IP adres van uw computer in. <br><br> Dit is een verplicht veld. This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. Dit is uw eigen naam. bijv. Jan Jansen. Als u iemand belt, kan deze naam getoond worden. SIP pro&xy: SIP pro&xy: The hostname, domain name or IP address of your SIP proxy. If this is the same value as your domain, you may leave this field empty. De host naam, domeinnaam of IP adres van uw SIP proxy. Als deze hetzelfde is als uw domeinnaam, dan mag u dit veld leeg laten. &SIP service provider: &SIP provider: &Password: &Paswoord: &User name*: Ge&bruikersnaam*: Your password for authentication. Uw paswoord voor authenticatie. &OK &OK Alt+O Alt+O &Cancel Ann&uleren Alt+C Alt+U None (direct IP to IP calls) Geen (directe IP naar IP gesprekken) Other Anders User profile wizard: Gebruikersprofiel wizard: You must fill in a user name for your SIP account. U moet een gebruikersnaam voor uw SIP account invullen. You must fill in a domain name for your SIP account. This could be the hostname or IP address of your PC if you want direct PC to PC dialing. U moet een domeinnaam voor uw SIP account invullen. Dit kan de host naam of IP adres van uw PC zijn als u direct van PC naar PC wilt bellen. Invalid value for SIP proxy. Ongeldige waarde voor SIP proxy. Invalid value for STUN server. Ongeldige waared voor STUN server. YesNoDialog &Yes &Ja &No &Nee incoming_call Answer Reject Afwijzen twinkle-1.10.1/src/gui/lang/twinkle_ru.ts000066400000000000000000007725671277565361200203440ustar00rootroot00000000000000 AddressCardForm Twinkle - Address Card Twinkle - Карточка абонента &Remark: &Описание: Infix name of contact. Префикс контакта. First name of contact. Имя контакта. &First name: &Имя: You may place any remark about the contact here. Вы можете вписать сюда любое описание этого контакта. &Phone: &Телефон: &Infix name: &Префикс имени: Phone number or SIP address of contact. Телефонный номер или SIP адрес контакта. Last name of contact. Фамилия контакта. &Last name: &Фамилия: &OK &Принять Alt+O Alt+O &Cancel &Отмена Alt+C Alt+C You must fill in a name. Вы должны заполнить поле имя. You must fill in a phone number or SIP address. Вы должны заполнить поле номер телефона или SIP адрес. AddressTableModel Name Имя Phone Телефон Remark Описание AuthenticationForm Twinkle - Authentication Twinkle - Аутентификация user No need to translate пользователь The user for which authentication is requested. Пользователь, для которого запрашивается аутентификация. profile No need to translate профиль The user profile of the user for which authentication is requested. Профиль пользователя, для которого запрашивается аутентификация. User profile: Профиль пользователя: User: Пользователь: &Password: &Пароль: Your password for authentication. Ваш пароль для аутентификации. Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Ваше аутентификационное имя для SIP. В большинстве случаем совпадает с именем пользователя SIP. Вы можете ввести здесь другое имя при необходимости. &User name: &Имя пользователя: &OK &OK &Cancel &Отмена Login required for realm: Имя требуемое для области: realm No need to translate область The realm for which you need to authenticate. Область, для которой нужна авторизация. BuddyForm Twinkle - Buddy Twinkle - Друзья Address book Адресная книга Select an address from the address book. Выберите адрес из адресной книги. &Phone: &Телефон: Name of your buddy. Имя вашего друга. &Show availability &Показать доступность Alt+S Alt+S Check this option if you want to see the availability of your buddy. This will only work if your provider offers a presence agent. Выберите эту опцию если вы хотите видеть доступность вашего друга. Это работает, только если ваш провайдер предоставляет услугу проверки доступности. &Name: &Имя: SIP address your buddy. SIP адрес вашего друга. &OK &Принять Alt+O Alt+O &Cancel &Отмена Alt+C Alt+C You must fill in a name. Вы должны заполнить имя. Invalid phone. Неправильный телефон. Failed to save buddy list: %1 Ошибка сохранения списка друзей: %1 BuddyList Availability Доступность unknown неизвестно offline не в сети online в сети request failed ошибка запроса request rejected запрос отклонён not published не опубликован failed to publish ошибка публикации Click right to add a buddy. Правый клик для добавления друзей. CoreAudio Failed to open sound card Ошибка открытия звуковой карты Failed to create a UDP socket (RTP) on port %1 Ошибка создания UDP сокета (RTP) на порте %1 Failed to create audio receiver thread. Ошибка создания принимаемого аудиопотока. Failed to create audio transmitter thread. Ошибка создания передаваемого аудиопотока. CoreCallHistory local user локальный пользователь remote user удалённый пользователь failure ошибка unknown неизвестно in входящий out исходящий DeregisterForm Twinkle - Deregister Twinkle - Разрегистрация deregister all devices разрегистрировать все устройства &OK &Принять &Cancel &Отмена DiamondcardProfileForm Fill in your account ID. Заполните ваш идентификатор учётной записи. Fill in your PIN code. Заполните ваш PIN код. A user profile with name %1 already exists. Профиль пользователя с именем %1 уже существует. DtmfForm Twinkle - DTMF Twinkle - DTMF Keypad Цифровая клавиатура 2 2 3 3 Over decadic A. Normally not needed. Нецифровое A. Обычно не нужно. 4 4 5 5 6 6 Over decadic B. Normally not needed. Нецифровое B. Обычно не нужно. 7 7 8 8 9 9 Over decadic C. Normally not needed. Нецифровое C. Обычно не нужно. Star (*) Звезда (*) 0 0 Pound (#) Решётка (#) Over decadic D. Normally not needed. Нецифровое D. Обычно не нужно. 1 1 &Close &Закрыть Alt+C Alt+C GUI Failed to create a %1 socket (SIP) on port %2 Ошибка создания %1 сокета (SIP) на порте %2 Override lock file and start anyway? Перехватить файл блокировки и запуститься сейчас? The following profiles are both for user %1 Следующие профили как для пользователя %1 You can only run multiple profiles for different users. Вы можете запускать только многократные профили для разных пользователей. If these are users for different domains, then enable the following option in your user profile (SIP protocol) Если эти пользователи для разных доменов, то включите следующий пункт в вашем профиле пользователя (протокол SIP) Use domain name to create a unique contact header Используйте доменное имя для создания уникального заголовка контакта Cannot find a network interface. Twinkle will use 127.0.0.1 as the local IP address. When you connect to the network you have to restart Twinkle to use the correct IP address. Не могу найти сетевой интерфейс. Twinkle будет использовать 127.0.0.1 как локальный IP адрес. Когда вы будете соединены с сетью, перезапустите Twinkle для использования правильного IP адреса. Line %1: incoming call for %2 Линия %1: входящий звонок для %2 Call transferred by %1 Звонок переведён к %1 Line %1: far end cancelled call. Линия %1: удалённая сторона прервала звонок. Line %1: far end released call. Линия %1: удалённая сторона закончила звонок. Line %1: SDP answer from far end not supported. Линия %1: SDP ответ от удалённой стороны не поддерживается. Line %1: SDP answer from far end missing. Линия %1: SDP ответ от удалённой стороны отсутствует. Line %1: Unsupported content type in answer from far end. Линия %1: Неподдерживаемый тип содержимого в ответе от удалённой стороны. Line %1: no ACK received, call will be terminated. Линия %1: не получен ACK, звонок прерван. Line %1: no PRACK received, call will be terminated. Линия %1: не получен PRACK, звонок будет прерван. Line %1: PRACK failed. Линия %1: PRACK ошибка. Line %1: failed to cancel call. Линия %1: Ошибка завершения звонка. Line %1: far end answered call. Линия %1: удалённая сторона ответила на звонок. Line %1: call failed. Линия %1: Ошибка звонка. The call can be redirected to: Звонок будет переведён к: Line %1: call released. Линия %1: соединение завершено. Line %1: call established. Линия %1: соединение установлено. Response on terminal capability request: %1 %2 Ответ на запрос органичений терминала: %1 %2 Terminal capabilities of %1 Ограничения терминала от %1 Accepted body types: Разрешённые типы содержимого: unknown неизвестный Accepted encodings: Подтверждённые кодировки: Accepted languages: Подтверждённые языки: Allowed requests: Разрешённые запросы: Supported extensions: Поддерживаемые расширения: none нету End point type: Тип конечной точки: Line %1: call retrieve failed. Линия %1: ошибка получения звонка. %1, registration failed: %2 %3 %1, ошибка регистрации: %2 %3 %1, registration succeeded (expires = %2 seconds) %1, регистрация завершена (устаревание = %2 секунд) %1, registration failed: STUN failure %1, ошибка регистрации: ошибка STUN %1, de-registration succeeded: %2 %3 %1, разрегистрация состоялась: %2 %3 %1, de-registration failed: %2 %3 %1, ошибка разрегистрации: %2 %3 %1, fetching registrations failed: %2 %3 %1, ошибка проверки регистрации: %2 %3 : you are not registered : вы не зарегистрированы : you have the following registrations : вы имеете следующие регистрации : fetching registrations... : проверка регистрации... Line %1: redirecting request to Линия %1: перенаправление запроса к Redirecting request to: %1 Перенаправление запроса к: %1 Line %1: DTMF detected: Линия %1: определён DTMF: invalid DTMF telephone event (%1) неправильное телефонное DTMF событие (%1) Line %1: send DTMF %2 Линия %1: посылка DTMF %2 Line %1: far end does not support DTMF telephone events. Линия %1: удалённая сторона не поддерживает DTMF события. Line %1: received notification. Линия %1: получено оповещение. Event: %1 Событие %1 State: %1 Состояние: %1 Reason: %1 Причина: %1 Progress: %1 %2 Прогресс: %1 %2 Line %1: call transfer failed. Линия %1: ошибка передачи звонка. Line %1: call successfully transferred. Линия %1: Перевод звонка завершён. Line %1: call transfer still in progress. Линия %1: перевод звонка. No further notifications will be received. Никакие дальнейшие уведомления не будут получены. Line %1: transferring call to %2 Линия %1. перевод звонка к %2 Transfer requested by %1 Перевод запрошен %1 Line %1: Call transfer failed. Retrieving original call. Линия %1: Ошибка передачи звонка. Получение оригинального звонка. %1, STUN request failed: %2 %3 %1, ошибка STUN запроса: %2 %3 %1, STUN request failed. %1, Ошибка STUN запроса. Redirecting call Перенаправление звонка User profile: Профиль пользователя: User: Пользователь: Do you allow the call to be redirected to the following destination? Вы разрешаете переводить звонки к следующему направлению? If you don't want to be asked this anymore, then you must change the settings in the SIP protocol section of the user profile. Если вы не хотите, чтобы вас об этом ещё раз спрашивало, то вы должны изменить настройки в разделе протокола SIP в профиле пользователя. Redirecting request Запрос перенаправления Do you allow the %1 request to be redirected to the following destination? Вы разрешаете перевести запрос %1 к следующему назначению? Transferring call Передача звонка Request to transfer call received from: Запрос перевода звонка получен от: Request to transfer call received. Запрос перевода звонка получен. Do you allow the call to be transferred to the following destination? Вы разрешаете передачу звонка к следующему назначению? Info: Информация: Warning: Внимание: Critical: Критическое: Firewall / NAT discovery... Обнаружение файервола/NAT... Abort Прервать Line %1 Линия %1 Click the padlock to confirm a correct SAS. Нажмите на замок для подтвердения корректности SAS. The remote user on line %1 disabled the encryption. Удалённый пользователь на линии %1 запретил шифрование. Line %1: SAS confirmed. Линия %1: SAS подтверждён. Line %1: SAS confirmation reset. Линия %1: сброс SAS подтверждения. %1, voice mail status failure. %1, ошибка статуса голосовой почты. %1, voice mail status rejected. %1, статус голосовой почты не принят. %1, voice mailbox does not exist. %1, ящик голосовой почты не существует. %1, voice mail status terminated. %1, прерван статус голосовой почты. Accepted by network Разрешён сетью Line %1: call rejected. Линия %1: звонок сброшен. Line %1: call redirected. Линия %1: звонок перенаправлен. Failed to start conference. Ошибка запуска конференции. Failed to save message attachment: %1 Ошибка сохранения вложения сообщения: %1 Transferred by: %1 Перевод: %1 Cannot open web browser: %1 Не могу открыть веб-браузер: %1 Configure your web browser in the system settings. Настройте веб-браузер в настройках системы. GetAddressForm Twinkle - Select address Twinkle - Выбор адреса &KAddressBook Адресная книга &KDE This list of addresses is taken from <b>KAddressBook</b>. Contacts for which you did not provide a phone number are not shown here. To add, delete or modify address information you have to use KAddressBook. Этот список адресов получен из <b>Адресной книги KDE</b>. Контакты, которые не содержат телефонного номера, здесь не показываются. Для добавления, удаления или модификации адресной информации вы должны использовать Адресную книгу KDE. &Show only SIP addresses &Показать только SIP адреса Alt+S Alt+S Check this option when you only want to see contacts with SIP addresses, i.e. starting with "<b>sip:</b>". Отметьте эту опцию если вы ходите видеть только контакты с SIP адресами, те которые начинаются с <b>sip:</b>". &Reload &Обновить Alt+R Alt+R Reload the list of addresses from KAddressbook. Перезагрузить список адресов из Адресной книги KDE. &Local address book &Локальная адресная книга Contacts in the local address book of Twinkle. Контакты из локальной адресной книги Twinkle. &Add &Добавить Alt+A Alt+A Add a new contact to the local address book. Добавить новый контакт в локальную адресную книгу. &Delete &Удалить Alt+D Alt+D Delete a contact from the local address book. Удалить контакт из локальной адресной книги. &Edit &Редактировать Alt+E Alt+E Edit a contact from the local address book. Редактировать контакт из локальной адресной книги. &OK &Принять Alt+O Alt+O &Cancel &Отмена Alt+C Alt+C <p>You seem not to have any contacts with a phone number in <b>KAddressBook</b>, KDE's address book application. Twinkle retrieves all contacts with a phone number from KAddressBook. To manage your contacts you have to use KAddressBook.<p>As an alternative you may use Twinkle's local address book.</p> <p>У Вас нет ни одного контакта с телефонным номером в <b>KAddressBook</b>(приложении KDE адресная книга). Twinkle получает все контакты с телефонными номерами из Адресной книги KDE. Для управления вашими контактами вы должны использовать KAddressBook.<p>Как альтернативу вы можете использовать локальную адресную книгу Twinkle.</p> GetProfileNameForm Twinkle - Profile name Twinkle - Имя профиля &OK &Принять &Cancel &Отмена Enter a name for your profile: Введите имя вашего профиля: <b>The name of your profile</b> <br><br> A profile contains your user settings, e.g. your user name and password. You have to give each profile a name. <br><br> If you have multiple SIP accounts, you can create multiple profiles. When you startup Twinkle it will show you the list of profile names from which you can select the profile you want to run. <br><br> To remember your profiles easily you could use your SIP user name as a profile name, e.g. <b>example@example.com</b> <b>Имя вашего профиля</b> <br><br> Профиль содержит ваши пользовательские настройки такие как имя пользователя и пароль. Вы должны дать каждому профилю отдельное имя <br><br> Если у вас несколько различных учётных записей SIP, вы можете создать несколько профилей. При запуске Twinkle покажет вам список профилей и вы сможете выбрать какой использовать при этом запуске. <br><br> Чтобы легко различать свои профили вы можете использовать ваше SIP имя пользователя как название профиля, например, <b>example@example.com</b> Cannot find .twinkle directory in your home directory. Не могу найти каталог .twinkle в вашем домашнем каталоге. Profile already exists. Профиль уже существует. Rename profile '%1' to: Переименовываю профиль '%1' в: HistoryForm Twinkle - Call History Twinkle - История звонков Time Время In/Out Входящий/исходящий From/To Откуда/Куда Subject Тема Status Статус Call details Детали звонка Details of the selected call record. Детали выбранной записи звонка. View Показать &Incoming calls &Входящие звонки Alt+I Alt+I Check this option to show incoming calls. Выберите эту опцию для показа входящих звонков. &Outgoing calls &Исходящие звонки Alt+O Alt+O Check this option to show outgoing calls. Выберите эту опцию для показа исходящих звонков. &Answered calls &Отвеченные звонки Alt+A Alt+A Check this option to show answered calls. Выберите эту опцию для показа отвеченных звонков. &Missed calls &Пропущенные звонки Alt+M Alt+M Check this option to show missed calls. Выберите эту опцию для показа пропущенных звонков. Current &user profiles only Только профиль данного &пользователя Alt+U Alt+U Check this option to show only calls associated with this user profile. Выберите эту опцию для показа звонков, ассоциированных с этим профилем пользователя. C&lear О&чистить Alt+L Alt+L <p>Clear the complete call history.</p> <p><b>Note:</b> this will clear <b>all</b> records, also records not shown depending on the checked view options.</p> <p>Очистить всю историю звонков.</p> <p><b>Заметка:</b> очищает <b>все</b> записи, записи удаляются безвозратно, и вы никогда не увидите их в любых вариантах просмотра.</p> Clo&se &Закрыть Alt+S Alt+S Close this window. Закрыть это окно. &Call &Звонить Alt+C Alt+C Call selected address. Звонить на выбранный адрес. Call... Звонить... Delete Удалить Call start: Звонок начат: Call answer: Звонок отвечен: Call end: Звонок закончен: Call duration: Продолжительность звонка: Direction: Направление: From: Откуда: To: Куда: Reply to: Ответить на: Referred by: Ссылаться на: Subject: Тема: Released by: Источник: Status: Статус: Far end device: Конечное устройство: User profile: Профиль пользователя: conversation разговор Re: Ответ: Number of calls: Количество звонков: ### ### Total call duration: Общая продолжительность звонка: IncomingCallPopup %1 calling %1 звонит InviteForm Twinkle - Call Twinkle - Звонить &To: &Кому: Optionally you can provide a subject here. This might be shown to the callee. Опционально вы можете ввести сюда тему. Возможно она будет показана удалённой стороне при звонке. Address book Адресная книга Select an address from the address book. Выберите адрес из адресной книги. The address that you want to call. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Адрес куда вы хотите позвонить. Это может быть полный SIP адрес, такой как <b>sip:example@example.com</b>, или просто часть имени пользователя или телефонный номер. Если вы не указываете полного адреса, Twinkle дополнит этот адрес, используя значение домена из вашего профиля пользователя. The user that will make the call. Пользователь, от которого вы будете производить звонок. &Subject: &Тема: &From: &Откуда: &Hide identity &Звонить анонимно Alt+H Alt+H <p> With this option you request your SIP provider to hide your identity from the called party. This will only hide your identity, e.g. your SIP address, telephone number. It does <b>not</b> hide your IP address. </p> <p> <b>Warning:</b> not all providers support identity hiding. </p> <p> С этой опцией вы запрашиваете у своего провайдера SIP услуг убрать ваши идентификационные данные из данных, направляемых к удалённой стороне. Эта опция только прячет ваши идентификационные данные такие как: ваш SIP адрес, телефонный номер, имя пользователя. Она <b>не</b> скрывает ваш IP адрес. </p> <p> <b>Внимание:</b> не все провайдеры поддерживают анонимные звонки. </p> &OK &Звонить &Cancel &Отмена Not all SIP providers support identity hiding. Make sure your SIP provider supports it if you really need it. Не все SIP провайдеры поддерживают анонимные звонки. Уточните у своего провайдера действительно ли он поддерживает это. F10 F10 LogViewForm Twinkle - Log Twinkle - Журнал Contents of the current log file (~/.twinkle/twinkle.log) Содержимое текущего журнала (~/.twinkle/twinkle.log) &Close &Закрыть Alt+C Alt+C C&lear О&чистить Alt+L Alt+L Clear the log window. This does <b>not</b> clear the log file itself. Очистить окно журнала. Это <b>не </b> стирает файл журнала и не очищает его. MessageForm Twinkle - Instant message Twinkle - Мгновенное сообщение &To: &Кому: The user that will send the message. Пользователь, от которого вы будете посылать сообщения. The address of the user that you want to send a message. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Адрес пользователя, которому вы посылаете сообщение. Это может быть полный SIP адрес, такой как <b>sip:example@example.com</b>, или просто часть имени пользователя, или телефонный номер. Если вы не указываете полного адреса, Twinkle дополнит этот адрес, используя значение домена из вашего профиля пользователя. Address book Адресная книга Select an address from the address book. Выберите адрес из адресной книги. &User profile: &Профиль пользователя: Conversation Общение Type your message here and then press "send" to send it. Введите ваше сообщение и нажмите "Отправить" для его отсылки. &Send &Отправить Alt+S Alt+S Send the message. Отправить сообщение. Delivery failure Ошибка доставки Delivery notification Сообщение доставки Send file... Отправить файл... Send file Отправить файл image size is scaled down in preview размер картинки уменьшен для просмотра Open with %1... Открыть в %1... Open with... Открыть в... Save attachment as... Сохранить вложение как... File already exists. Do you want to overwrite this file? Файл уже существует. Перезаписать данный файл? Failed to save attachment. Ошибка сохранения вложения. %1 is typing a message. %1 пишет сообщение. F10 F10 Size Размер MessageFormView sending message отправка сообщения MphoneForm Twinkle Twinkle Buddy list Список друзей You can create a separate buddy list for each user profile. You can only see availability of your buddies and publish your own availability if your provider offers a presence server. Вы можете создать раздельные листы друзей для каждого профиля. Вы можете только видеть доступность друзей и публиковать вашу личную доступность, если провайдер поддерживает функцию доступности на сервере. &Call: Label in front of combobox to enter address &Позвонить: The address that you want to call. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Адрес куда вы хотите позвонить. Это может быть полный SIP адрес, такой как <b>sip:example@example.com</b>, или просто часть имени пользователя, или телефонный номер. Если вы не указываете полного адреса, Twinkle дополнит этот адрес, используя значение домена из вашего профиля пользователя. Address book Адресная книга Select an address from the address book. Выберите адрес из адресной книги. Dial Набрать Dial the address. Набрать адрес. &User: &Пользователь: The user that will make the call. Пользователь, от которого вы будете производить звонок. Auto answer indication. Индикатор автоответа. Call redirect indication. Индикатор перенаправления звонков. Do not disturb indication. Индикатор "Не тревожить". Message waiting indication. Индикатор ожидания сообщения. Missed call indication. Индикатор пропущенных звонков. Registration status. Статус регистрации. Display Монитор Line status Статус линий Line &1: Линия &1: Alt+1 Alt+1 Click to switch to line 1. Нажмите для переключения к линии 1. From: Откуда: To: Куда: Subject: Тема: idle No need to translate простой Transferring call Передача звонка sas No need to translate sas Short authentication string Короткая строка авторизации g711a/g711a No need to translate g711a/g711a Audio codec Аудио кодек 0:00:00 0:00:00 Call duration Продолжительность звонка sip:from No need to translate sip:от sip:to No need to translate sip:до subject No need to translate тема photo No need to translate фото Line &2: Линия &2: Alt+2 Alt+2 Click to switch to line 2. Нажмите для переключения к линии 2. &File &Файл &Edit &Редактировать C&all З&вонить Activate line Активировать линию &Message &Чат &Registration &Регистрация &Services &Сервисы &View &Показать &Help &Справка Quit Выход &Quit &Выход Ctrl+Q Ctrl+Q About Twinkle О программе Twinkle &About Twinkle &О программе Twinkle Call someone Позвонить кому-либо F5 F5 Answer incoming call Ответить на входящий звонок F6 F6 Release call Завершить звонок Esc Esc Reject incoming call Отклонить входящий звонок F8 F8 Put a call on hold, or retrieve a held call Поставить звонок на удержание или ответить на второй звонок Redirect incoming call without answering Перенаправить входящий звонок без ответа Open keypad to enter digits for voice menu's Открывает панель цифровых клавиш для использования в голосовых меню Register Регистрация &Register &Регистрация Deregister Разрегистрация &Deregister &Разрегистрация Deregister this device Разрегистрация этого устройства Show registrations Показать регистрации &Show registrations &Показать регистрации Terminal capabilities Ограничения терминала Request terminal capabilities from someone Запрос органичений терминала от кого-либо Do not disturb Не беспокоить &Do not disturb &Не беспокоить Call redirection Перенаправление звонка Call &redirection... &Перенаправление звонка... Repeat last call Повторить последний звонок F12 F12 About Qt О Qt About &Qt О &Qt User profile Профиль пользователя &User profile... &Профиль пользователя... Join two calls in a 3-way conference Объединить два звонка в единую 3-х стороннюю конференцию Mute a call Отключить микрофон Transfer call Переключить звонок на другого абонента System settings Системные настройки &System settings... &Системные настройки... Deregister all Разрегистрировать всех Deregister &all Разрегистрировать &всех Deregister all your registered devices Разрегистрировать все ваши зарегистрированные устройства Auto answer Автоответ &Auto answer &Автоответ Log Журнал &Log... &Журнал... Call history История звонков Call &history... &История звонков... F9 F9 Change user ... Сменить пользователя ... &Change user ... &Сменить пользователя ... Activate or de-activate users Активировать или деактивировать пользователей What's This? Что это? What's &This? Что &это? Shift+F1 Shift+F1 Line 1 Линия 1 Line 2 Линия 2 &Display &Монитор Voice mail Голосовая почта &Voice mail &Голосовая почта Access voice mail Доступ к голосовой почте F11 F11 Msg Чат Instant &message... Мгновенное &сообщение... Instant message Сообщения &Buddy list Список &друзей &Call... &Звонить... &Edit... &Редактировать... &Delete &Удалить O&ffline &Не в сети &Online &В сети &Change availability &Изменить доступность &Add buddy... &Добавить друзей... idle простой dialing набор номера attempting call, please wait производится звонок, пожалуйста, ждите incoming call входящий звонок establishing call, please wait установка соединения, пожалуйста, ожидайте established установлено established (waiting for media) установлено (ожидание медиапотока) releasing call, please wait завершение соединения, пожалуйста, ожидайте unknown state неизвестное состояние Voice is encrypted Звук зашифрован Click to confirm SAS. Нажмите для подтверждения SAS. Click to clear SAS verification. Нажмите для очистки проверки SAS. Transfer consultation Передача с разговором User: Пользователь: Call: Звонок: Hide identity Звонить анонимно Registration status: Статус регистрации: Registered Зарегистрирован Failed Oшибка Not registered Не зарегистрирован Click to show registrations. Нажмите для показа регистрации. No users are registered. Нет зарегистрированных пользователей. %1 new, 1 old message %1 новое, 1 старое сообщение %1 new, %2 old messages %1 новое, %2 старых сообщений 1 new message 1 новое сообщение %1 new messages %1 новых сообщений 1 old message 1 старое сообщение %1 old messages %1 старых сообщений Messages waiting Ожидание сообщений No messages Нет сообщений <b>Voice mail status:</b> <b>Статус голосовой почты:</b> Failure Ошибка Unknown Неизвестно Click to access voice mail. Нажмите для доступа к голосовой почте. Do not disturb active for: Не беспокоить активно для: Redirection active for: Перенаправление активно для: Auto answer active for: Автоответ активен для: Click to activate/deactivate Нажмите для активации/деактивации Click to activate Нажмите для активации Do not disturb is not active. Не беспокоить не активно. Redirection is not active. Перенаправление не активно. Auto answer is not active. Автоответ не активен. Click to see call history for details. Нажмите для просмотра подробностей истории звонков. You have no missed calls. У вас нет пропущенных звонков. You missed 1 call. У вас есть 1 пропущенный звонок. You missed %1 calls. У вас %1 пропущенных звонков. Starting user profiles... Запуск профиля пользователя... The following profiles are both for user %1 Следующие профили как для пользователя %1 You can only run multiple profiles for different users. Вы можете запускать только многократные профили для разных пользователей. You have changed the SIP UDP port. This setting will only become active when you restart Twinkle. Вы изменили SIP UDP порт. Эта настройка применится только после перезапуска Twinkle. not provisioned не предусмотрен You must provision your voice mail address in your user profile, before you can access it. Вы должны предоставить адрес голосовой почты в вашем профиле перед доступом к ней. The line is busy. Cannot access voice mail. Линия занята. Не могу получить доступ к голосовой почте. The voice mail address %1 is an invalid address. Please provision a valid address in your user profile. Адрес голосовой почты %1 не является верным. Пожалуйста, предоставьте правильный адрес в вашем профиле пользователя. Failed to save buddy list: %1 Ошибка сохранения списка друзей: %1 F10 F10 Diamondcard Алмазная карта Manual Справочник &Manual &Справочник Sign up Зарегистрироваться &Sign up... &Зарегистрироваться... Recharge... Перезарядка... Balance history... История баланса... Call history... История звонков... Admin center... Центр админа... Recharge Перезарядка Balance history История баланса Admin center Центр админа Call Звон &Answer &Ответить Answer Ответ &Bye &Завершить Bye Заверш &Reject &Отклонить Reject Отклон &Hold &Удержать Hold Удерж R&edirect... П&еренаправить... Redirect Направ &Dtmf... &Dtmf... Dtmf Dtmf &Terminal capabilities... &Ограничения терминала... &Redial &Повторить Redial Повт &Conference &Конференция Conf Конф &Mute &Выключить звук Mute Вык.Зв Trans&fer... Пере&ключить... Xfer Перекл NumberConversionForm Twinkle - Number conversion Twinkle - Преобразователь номеров &Match expression: &Совпадение: &Replace: &Замена: Perl style format string for the replacement number. Perl стиль формата строки для замены номера. Perl style regular expression matching the number format you want to modify. Perl стиль регулярного выражения совпадения формата номера, который вы хотите изменить. &OK &OK Alt+O Alt+O &Cancel &Отмена Alt+C Alt+C Match expression may not be empty. Совпадающее выражение не может быть пустым. Replace value may not be empty. Заменяющее значение не может быть пустым. Invalid regular expression. Неправильное регулярное выражение. RedirectForm Twinkle - Redirect Twinkle - Перенаправление Redirect incoming call to Перенаправление входящего звонка к You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. Вы можете определить до 3 направлений, куда будут перенаправлятся звонки. Если первое направление не отвечает на вызов, то звонок перейдёт на второе направление и так далее. &3rd choice destination: &3-й выбор направления: &2nd choice destination: &2-й выбор направления: &1st choice destination: &1-й выбор направления: Address book Адресная книга Select an address from the address book. Выберите адрес из адресной книги. &OK &Принять &Cancel &Отмена F10 F10 F12 F12 F11 F11 SelectNicForm Twinkle - Select NIC Twinkle - Выбор сетевого интерфейса Select the network interface/IP address that you want to use: Выберите сетевой интерфейс/IP адрес, который вы будете использовать: You have multiple IP addresses. Here you must select which IP address should be used. This IP address will be used inside the SIP messages. В вашей системе несколько IP адресов. Здесь вы должны выбрать какой из IP адресов нужно использовать. Этот IP адрес будет использоваться вне SIP сообщений (заголовки ip пакетов). Set as default &IP Установить как &IP по умолчанию Alt+I Alt+I Make the selected IP address the default IP address. The next time you start Twinkle, this IP address will be automatically selected. Сделать выбранный IP адрес адресом по умолчанию. В следующий раз когда Twinkle будет запускаться, этот IP адрес будет выбран автоматически. Set as default &NIC Выбрать как сетевую карту по &умолчанию Alt+N Alt+N Make the selected network interface the default interface. The next time you start Twinkle, this interface will be automatically selected. Сделать выбранный сетевой интерфейс интерфейсом по умолчанию. В следующий раз когда Twinkle будет запускатся, этот интерфейс будет выбран автоматически. &OK &Принять Alt+O Alt+O If you want to remove or change the default at a later time, you can do that via the system settings. Если вы захотите удалить или изменить профиль по умолчанию позже, то вы можете сделать это через системные настройки. SelectProfileForm Twinkle - Select user profile Twinkle - Выбор пользовательского профиля Select user profile(s) to run: Выберите пользовательский(ие) профиль(ли) для запуска: Tick the check boxes of the user profiles that you want to run and press run. Поставьте отметки на профилях, которые вы хотите запустить, и нажмите запуск. Create a new profile with the profile editor. Создание нового профиля с помощью редактора профилей. &Wizard &Мастер Alt+W Alt+W Create a new profile with the wizard. Создание нового профиля с помощью мастера профилей. &Edit &Изменить Alt+E Alt+E Edit the highlighted profile. Редактировать выделенный профиль. &Delete &Удалить Alt+D Alt+D Delete the highlighted profile. Удалить выделенный профиль. Ren&ame Пер&еименовать Alt+A Alt+A Rename the highlighted profile. Переименовать выделенный профиль. &Set as default &Уст. по умолчанию Alt+S Alt+S Make the selected profiles the default profiles. The next time you start Twinkle, these profiles will be automatically run. Сделать выбранный профиль как профиль по умолчанию. При следующих запусках Twinkle этот профиль будет автоматически запущен. &Run &Запуск Alt+R Alt+R Run Twinkle with the selected profiles. Запустить Twinkle с выбраным профилем. S&ystem settings С&истемные настройки Alt+Y Alt+Y Edit the system settings. Редактировать системные настройки. &Cancel &Отмена Alt+C Alt+C <html>Before you can use Twinkle, you must create a user profile.<br>Click OK to create a profile.</html> <html>Перед тем как использовать Twinkle, вы должны создать пользовательский профиль.<br>Нажмите OK для создания профиля.</html> &Profile editor &Редактор профилей <html>Next you may adjust the system settings. You can change these settings always at a later time.<br><br>Click OK to view and adjust the system settings.</html> <html>Далее вы можете определить системные настройки. Вы можете изменить эти настройки позже.<br><br>Нажмите OK для просмотра и настройки системных настроек.</html> You did not select any user profile to run. Please select a profile. Вы не выбрали ни одного профиля для запуска. Пожалуйста, выберите профиль. Are you sure you want to delete profile '%1'? Вы уверены, что хотите удалить профиль '%1'? Delete profile Удалить профиль Failed to delete profile. Ошибка удаления профиля. Failed to rename profile. Ошибка переименования профиля. <p>If you want to remove or change the default at a later time, you can do that via the system settings.</p> <p>Если вы захотите удалить или изменить профиль по умолчанию позже, то вы можете сделать это через системные настройки.</p> Cannot find .twinkle directory in your home directory. Не могу найти .twinkle папку в вашей домашней директории. Create profile Создать профиль Ed&itor &Редактор Alt+I Alt+I Dia&mondcard &Алмазная карта Alt+M Alt+M Modify profile Изменить профиль Startup profile Автозапуск профиля &Diamondcard А&лмазная карта Create a profile for a Diamondcard account. With a Diamondcard account you can make worldwide calls to regular and cell phones and send SMS messages. Создать профиль для учётной записи Алмазной карты. С аккаунта Алмазной карты Вы можете по всему миру делать звонки на обычные и мобильные телефоны, и отправлять SMS сообщения. <html>You can use the profile editor to create a profile. With the profile editor you can change many settings to tune the SIP protocol, RTP and many other things.<br><br>Alternatively you can use the wizard to quickly setup a user profile. The wizard asks you only a few essential settings. If you create a user profile with the wizard you can still edit the full profile with the profile editor at a later time.<br><br> <html>Для создания нового профиля вы можете использовать редактор профилей. В редакторе профилей вы можете гибко изменить настройки для SIP протокола, RTP и множество других тонких настроек.<br><br>Как альтернативу вы можете использовать мастер для быстрой настройки. Мастер спросит у вас только самые необходимые настройки. Если вы создаёте профиль мастером, то позже сможете редактировать его редактором профилей.<br><br> You can create a Diamondcard account to make worldwide calls to regular and cell phones and send SMS messages.<br><br> Вы можете создать учётную запись Алмазной карты чтобы делать звонки по всему миру на обычные и мобильные телефоны, и отправлять SMS сообщения.<br><br> Choose what method you wish to use.</html> Выберите метод который вы будете использовать.</html> SelectUserForm Twinkle - Select user Twinkle - Выбор пользователя &Cancel &Отмена Alt+C Alt+C &Select all &Выбрать все Alt+S Alt+S &OK &Принять Alt+O Alt+O C&lear all О&чистить все Alt+L Alt+L purpose No need to translate назначение Register Регистрация Select users that you want to register. Выберите пользователей, которых надо зарегистрировать. Deregister Разрегистрация Select users that you want to deregister. Выберите пользователей, которых надо разрегистрировать. Deregister all devices Разрегистрировать все устройства Select users for which you want to deregister all devices. Выберите пользователей, для которых вы хотите "разрегистрировать все устройства". Do not disturb Не беспокоить Select users for which you want to enable 'do not disturb'. Выберите пользователей, для которых вы хотите включить "не беспокоить". Auto answer Автоответ Select users for which you want to enable 'auto answer'. Выберите пользователей, для которых вы хотите включить "автоответ". SendFileForm Twinkle - Send File Twinkle - Отправить файл Select file to send. Выберите файл для отправки. &File: &Файл: &Subject: &Тема: &OK &Отправить Alt+O Alt+O &Cancel &Отмена Alt+C Alt+C File does not exist. Файл не существует. Send file... Отправить файл... SrvRedirectForm Twinkle - Call Redirection Twinkle - Перенаправление звонков User: Пользователь: There are 3 redirect services:<p> <b>Unconditional:</b> redirect all calls </p> <p> <b>Busy:</b> redirect a call if both lines are busy </p> <p> <b>No answer:</b> redirect a call when the no-answer timer expires </p> Существует 3 сервиса перенаправления<p> <b>Безусловный:</b> перенаправляет все звонки </p> <p> <b>Занятый:</b> перенаправляет звонок ели обе линии заняты </p> <p> <b>Без ответа:</b> перенаправляет звонок когда срабатывает таймер на отсутствие ответа </p> &Unconditional &Безусловный &Redirect all calls &Перенаправление всех звонков Alt+R Alt+R Activate the unconditional redirection service. Активировать сервис безусловной переадресации. Redirect to Перенаправить к You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. Вы можете определить до 3 направлений, куда будут перенаправлятся звонки. Если первое направление не отвечает на вызов, то звонок перейдёт на второе направление и так далее. &3rd choice destination: &3-й выбор направления: &2nd choice destination: &2-й выбор направления: &1st choice destination: &1-й выбор направления: Address book Адресная книга Select an address from the address book. Выберите адрес из адресной книги. &Busy &Занятый &Redirect calls when I am busy &Перенаправлять звонки когда я занят Activate the redirection when busy service. Активировать перенаправление когда я занят. &No answer &Нет ответа &Redirect calls when I do not answer &Перенаправлять звонки когда я не отвечаю Activate the redirection on no answer service. Активировать перенаправление когда я не отвечаю. &OK &Принять Alt+O Alt+O Accept and save all changes. Принять и сохранить изменения. &Cancel &Отмена Alt+C Alt+C Undo your changes and close the window. Отменить все измения и закрыть окно. You have entered an invalid destination. Вы ввели неверное направление. F10 F10 F11 F11 F12 F12 SysSettingsForm Twinkle - System Settings Twinkle - Системные настройки General Основные Audio Аудио Ring tones Сигналы Address book Адресная книга Network Сеть Log Журнал Select a category for which you want to see or modify the settings. Выберите категорию для просмотра и модификации настроек. &OK &Принять Alt+O Alt+O Accept and save your changes. Принять и сохранить изменения. &Cancel &Отмена Alt+C Alt+C Undo all your changes and close the window. Отменить все измения и закрыть окно. Sound Card Звуковая Карта Select the sound card for playing the ring tone for incoming calls. Выберите звуковую карту для проигрывания сигнала вызова для входящих звонков. Select the sound card to which your microphone is connected. Выберите звуковую карту, к которой подключен ваш микрофон. Select the sound card for the speaker function during a call. Выберите звуковую карту, к которой подключены ваши колонки или наушники. &Speaker: &Колонки: &Ring tone: &Сигнал вызова: Other device: Другое устройство: &Microphone: &Микрофон: &Validate devices before usage &Проверять устройства перед использованием Alt+V Alt+V <p> Twinkle validates the audio devices before usage to avoid an established call without an audio channel. <p> On startup of Twinkle a warning is given if an audio device is inaccessible. <p> If before making a call, the microphone or speaker appears to be invalid, a warning is given and no call can be made. <p> If before answering a call, the microphone or speaker appears to be invalid, a warning is given and the call will not be answered. Advanced Дополнительно OSS &fragment size: OSS &размер фрагмента: 16 16 32 32 64 64 128 128 256 256 The ALSA play period size influences the real time behaviour of your soundcard for playing sound. If your sound frequently drops while using ALSA, you might try a different value here. ALSA &play period size: ALSA размер периода &проигрывания: &ALSA capture period size: ALSA размер периода &записи: The OSS fragment size influences the real time behaviour of your soundcard. If your sound frequently drops while using OSS, you might try a different value here. The ALSA capture period size influences the real time behaviour of your soundcard for capturing sound. If the other side of your call complains about frequently dropping sound, you might try a different value here. &Max log size: &Максимальный размер журнала: The maximum size of a log file in MB. When the log file exceeds this size, a backup of the log file is created and the current log file is zapped. Only one backup log file will be kept. Максимальный размер файла журнала в МБ. Когда журнал достигает этого размера, создаётся архивная копия и текущий журнал удаляется. Остаётся только архивная копия. MB МБ Log &debug reports Записывать &отладочные отчёты Alt+D Alt+D Indicates if reports marked as "debug" will be logged. Указывает будут ли записываться отладочные сообщения в журнал. Log &SIP reports Записывать &SIP отчёты Alt+S Alt+S Indicates if SIP messages will be logged. Указывает будут ли записываться SIP сообщения в журнал. Log S&TUN reports Записывать S&TUN отчёты Alt+T Alt+T Indicates if STUN messages will be logged. Указывает будут ли записываться STUN сообщения в журнал. Log m&emory reports Записывать отчёты п&амяти Alt+E Alt+E Indicates if reports concerning memory management will be logged. Указывает будут ли записываться сообщения, касающиеся управления памятью, в журнал. System tray Системный лоток Create &system tray icon on startup Создавать значок в &системном лотке при запуске Enable this option if you want a system tray icon for Twinkle. The system tray icon is created when you start Twinkle. Включите эту опцию если вы хотите видеть значок Twinkle в системном лотке. Значок в системном лотке создаётся при старте Twinkle. &Hide in system tray when closing main window &Прятать в системном лотке при закрытии главного окна Alt+H Alt+H Enable this option if you want Twinkle to hide in the system tray when you close the main window. Включите эту опцию если вы хотите, чтобы Twinkle прятался в системном лотке, когда вы закрываете основное окно. Startup Запуск S&tartup hidden in system tray При &запуске свернуться в системный лоток Next time you start Twinkle it will immediately hide in the system tray. This works best when you also select a default user profile. При следующих запусках Twinkle будет постоянно прятаться в системном лотке. Это удобно когда вы выбрали профиль пользователя по умолчанию. If you always use the same profile(s), then you can mark these profiles as default here. The next time you start Twinkle, you will not be asked to select which profiles to run. The default profiles will automatically run. Если вы всегда используете одинаковый(ые) профиль(ли), тогда вы можете отметить этот профиль(ли) как профиль по умолчанию. При следующем запуске Twinkle он не будет спрашивать выбор профиля для запуска. Профиль по умолчанию загружается автоматически. Services Сервисы Call &waiting &Ожидание звонка Alt+W Alt+W With call waiting an incoming call is accepted when only one line is busy. When you disable call waiting an incoming call will be rejected when one line is busy. При ожидании звонка если одна из линий занята, то входящий звонок будет разрешён. Если вы запретите ожидание звонка, то если одна из линий занята, входящие звонки будут отклонены. Hang up &both lines when ending a 3-way conference call. Прерывать &обе линии при завершении 3-х сторонней конференции. Alt+B Alt+B Hang up both lines when you press bye to end a 3-way conference call. When this option is disabled, only the active line will be hung up and you can continue talking with the party on the other line. Прерывать обе линии при завершении 3-х сторонней конференции. Если эта опция отключена, только активная линия будет прервана, и вы сможете продолжить разговор с удалённой стороной на другой линии. &Maximum calls in call history: &Максимальное количество звонков в истории: The maximum number of calls that will be kept in the call history. Максимальное количество звонков, которые будут сохранятся в истории звонков. &Auto show main window on incoming call after &Автоматически показывать главное окно при входящем звонке после Alt+A Alt+A When the main window is hidden, it will be automatically shown on an incoming call after the number of specified seconds. Когда главное окно скрыто, оно будет автоматически показано при входящем звонке после указанного количества секунд. Number of seconds after which the main window should be shown. Число секунд, после которого главное окно будет показано. secs секунд Maximum allowed size (0-65535) in bytes of an incoming SIP message over UDP. Максимально разрешённый размер (0-65535) в байтах входящего SIP сообщения через UDP. &SIP port: &SIP порт: &RTP port: &RTP порт: Max. SIP message size (&TCP): Макс. размер SIP сообщения (&TCP): The UDP/TCP port used for sending and receiving SIP messages. UDP/TCP порт используемый для отправки и получения SIP сообщений. Max. SIP message size (&UDP): Макс. размер SIP сообщения (&UDP): Maximum allowed size (0-4294967295) in bytes of an incoming SIP message over TCP. Максимально разрешённый размер (0-4294967295) в байтах для входящего SIP сообщения поверх TCP. The UDP port used for sending and receiving RTP for the first line. The UDP port for the second line is 2 higher. E.g. if port 8000 is used for the first line, then the second line uses port 8002. When you use call transfer then the next even port (eg. 8004) is also used. UDP порт, используемый для отправки и приёма RTP для первой линии. UDP порт для второй линии на 2 больше. Если для первой линии используется порт 8000, значит вторая линия использует порт 8002. Когда вы используете передачу звонка, используется следующий свободный порт (8004). Ring tone Звонок &Play ring tone on incoming call &Играть звонок при входящем вызове Alt+P Alt+P Indicates if a ring tone should be played when a call comes in. Указывает будет ли проигрываться сигнал вызова при входящем вызове. &Default ring tone &Стандартный звонок Play the default ring tone when a call comes in. Проигрывать сигнал вызова по умолчанию при входящем звонке. C&ustom ring tone С&вой звонок Alt+U Alt+U Play a custom ring tone when a call comes in. Проигрывать свой сигнал по умолчанию при входящем звонке. Specify the file name of a .wav file that you want to be played as ring tone. Определите имя файла из *.wav файлов, который будет проигрываться как сигнал вызова. Select ring tone file. Выберите файл сигнала вызова. Ring back tone Гудок вызова P&lay ring back tone when network does not play ring back tone И&грать гудок когда сеть не играет сигнала вызова Alt+L Alt+L <p> Play ring back tone while you are waiting for the far-end to answer your call. </p> <p> Depending on your SIP provider the network might provide ring back tone or an announcement. </p> D&efault ring back tone С&тандартный гудок вызова Play the default ring back tone. Играть стандартный гудок при исходящем вызове. Cu&stom ring back tone С&вой гудок вызова Play a custom ring back tone. Играть свой гудок вызова. Specify the file name of a .wav file that you want to be played as ring back tone. Определите файл из .wav файлов, который будет проигрываться как гудок вызова. Select ring back tone file. Выберите файл гудка вызова. &Lookup name for incoming call &Искать имя при входящем звонке On an incoming call, Twinkle will try to find the name belonging to the incoming SIP address in your address book. This name will be displayed. При входящем звонке Twinkle будет пытаться найти имя в вашей адресной книге, которое соответствует входящему SIP адресу. Это имя будет отображено. Ove&rride received display name За&менять полученное имя абонента Alt+R Alt+R The caller may have provided a display name already. Tick this box if you want to override that name with the name you have in your address book. Звонящий может предоставить отображаемое имя. Отметьте эту опцию если вы хотите заменить его данные данными из вашей адресной книги. Lookup &photo for incoming call Искать &фотографию при входящем звонке Lookup the photo of a caller in your address book and display it on an incoming call. Искать фото звонящего в вашей адресной книге и отобразить его при входящем звонке. Ring tones Description of .wav files in file dialog Сигналы вызова Choose ring tone Выберите сигнал вызова Ring back tones Description of .wav files in file dialog Гудок вызова Choose ring back tone Выберите гудок вызова W&eb browser command: Команда &веб-браузера: Command to start your web browser. If you leave this field empty Twinkle will try to figure out your default web browser. Команда для запуска веб-браузера. Если оставить это поле пустым, то Twinkle будет пытаться использовать веб-браузер по умолчанию. 512 512 1024 1024 Tip: for crackling sound with PulseAudio, set play period size to maximum. Совет: при треске с PulseAudio установите размер периода проигрывания на максимум. Enable in-call OSD Включить входящие звонки OSD SysTrayPopup Answer Ответить Reject Отклонить Incoming Call Входящий звонок TermCapForm Twinkle - Terminal Capabilities Twinkle - Ограничения клиента &From: &Откуда: Get terminal capabilities of Получить ограничения клиента от &To: &Откуда: The address that you want to query for capabilities (OPTION request). This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Address book Адресная книга Select an address from the address book. Выберите адрес из адресной книги. &OK &Принять &Cancel &Отмена F10 F10 TransferForm Twinkle - Transfer Twinkle - Переключение Transfer call to Переключить звонок к &To: &Кому: The address of the person you want to transfer the call to. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Адрес абонента, которому вы хотите перевести звонок. Это может быть полный адрес вида <b>sip:example@example.com</b> , только пользовательская часть или номер телефона из полного адреса. Когда вы не указываете полный адрес, Twinkle дополняет адрес, используя имя домена из вашего профиля пользователя. Address book Адресная книга Select an address from the address book. Выберите адрес из адресной книги. &Blind transfer &Слепой перевод Alt+B Alt+B Transfer the call to a third party without contacting that third party yourself. Передача звонка другому абоненту без разговора с ним. T&ransfer with consultation П&ередача с разговором Alt+R Alt+R Before transferring the call to a third party, first consult the party yourself. Перед передачей звонка другому абоненту вы можете предварительно переговорить с ним. Transfer to other &line Передача на другую &линию Alt+L Alt+L Connect the remote party on the active line with the remote party on the other line. Подключить удалённую сторону на активную линию с удалённой стороной на другой линии. &OK &OK Alt+O Alt+O &Cancel &Отмена F10 F10 TwinkleCore Anonymous Неизвестный Warning: Внимание: Failed to create log file %1 . Ошибка создания файла журнала: %1 . Cannot open file for reading: %1 Не могу открыть файл для чтения: %1 File system error while reading file %1 . Ошибка файловой системы при чтении файла %1 . Cannot open file for writing: %1 Не могу открыть файл для записи: %1 File system error while writing file %1 . Ошибка файловой системы при записи файла %1 . Excessive number of socket errors. Большое число ошибок сокета. Built with support for: Собрано с поддержкой для: Contributions: При участии: This software contains the following software from 3rd parties: Данная программа содержит следующее програмное обеспечение от третьих сторон: * GSM codec from Jutta Degener and Carsten Bormann, University of Berlin * GSM кодек от Jutta Degener and Carsten Bormann, University of Berlin * G.711/G.726 codecs from Sun Microsystems (public domain) * G.711/G.726 кодек от Sun Microsystems (public domain) * iLBC implementation from RFC 3951 (www.ilbcfreeware.org) * iLBC реализация по RFC 3951 (www.ilbcfreeware.org) * Parts of the STUN project at http://sourceforge.net/projects/stun * Часть из STUN проекта на http://sourceforge.net/projects/stun * Parts of libsrv at http://libsrv.sourceforge.net/ * Часть из libsrv на http://libsrv.sourceforge.net/ For RTP the following dynamic libraries are linked: Для RTP следующие динамические библиотеки были связаны: Translated to english by <your name> Перевод на русский язык: Ходоренко Михаил chodorenko@mail.ru Directory %1 does not exist. Директория %1 не существует. Cannot open file %1 . Не могу открыть файл %1 . %1 is not set to your home directory. %1 не установлена в вашу домашнюю директорию. Directory %1 (%2) does not exist. Директория %1 (%2) не существует. Cannot create directory %1 . Не могу создать директорию %1 . %1 is already running. Lock file %2 already exists. %1 уже запушен. файл блокировки %2 уже существует. Cannot create %1 . Не могу создать %1 . Syntax error in file %1 . Синтаксическая ошибка в файле %1 . Failed to backup %1 to %2 Ошибка создания резервной копии %1 в %2 unknown name (device is busy) неизвестное имя (устройство занято) Default device Устройство по умолчанию Cannot access the ring tone device (%1). Не могу получить доступ к устройству звукового сигнала (%1). Cannot access the speaker (%1). Не могу получить доступ к аудиовыходу (%1). Cannot access the microphone (%1). Не могу получить доступ к входу микрофона (%1). Cannot receive incoming TCP connections. Не могу получить входящие TCP соединения. Call transfer - %1 Перевод звонка - %1 Sound card cannot be set to full duplex. Звуковая карта не может установить полнодуплексный режим. Cannot set buffer size on sound card. Не могу установить размер буфера на звуковой карте. Sound card cannot be set to %1 channels. Звуковая карта не может быть установлена для %1 каналов. Cannot set sound card to 16 bits recording. Не могу настроить звуковую карту для 16-битной записи. Cannot set sound card to 16 bits playing. Не могу настроить звуковую карту для 16-битного проигрывания. Cannot set sound card sample rate to %1 Не могу настроить частоту дискретизации звуковой карты %1 Opening ALSA driver failed Ошибка открытия ALSA драйвера Cannot open ALSA driver for PCM playback Не могу открыть ALSA драйвер для воспроизведения PCM Cannot open ALSA driver for PCM capture Не могу открыть ALSA драйвер для захвата PCM Cannot resolve STUN server: %1 Не могу определить имя STUN сервера: %1 You are behind a symmetric NAT. STUN will not work. Configure a public IP address in the user profile and create the following static bindings (UDP) in your NAT. public IP: %1 --> private IP: %2 (SIP signaling) внешний IP: %1 --> внутренний IP: %2 (SIP оповещение) public IP: %1-%2 --> private IP: %3-%4 (RTP/RTCP) внешний IP: %1-%2 --> внутренний IP: %3-%4 (RTP/RTCP) Cannot reach the STUN server: %1 Не могу получить доступ к STUN серверу: %1 If you are behind a firewall then you need to open the following UDP ports. Если вы находитесь за файерволом, вам нужно открыть следующие UDP порты. Port %1 (SIP signaling) Порт %1 (SIP оповещение) Ports %1-%2 (RTP/RTCP) Порты %1-%2 (RTP/RTCP) NAT type discovery via STUN failed. Ошибка определения типа NAT через STUN. Failed to create file %1 Ошибка создания файла %1 Failed to write data to file %1 Ошибка записи данных в файл %1 Failed to send message. Ошибка отправки сообщения. Cannot lock %1 . Не могу заблокировать %1 . UserProfileForm Twinkle - User Profile Twinkle - Профиль пользователя User profile: Профиль пользователя: Select which profile you want to edit. Выберите профиль пользователя для редактирования. User Пользователь SIP server SIP сервер Voice mail Голосовая почта Instant message Сообщения Presence Доступность RTP audio RTP аудио SIP protocol SIP протокол Transport/NAT Транспорт/NAT Address format Формат адреса Timers Таймеры Ring tones Звуковые сигналы Scripts Скрипты Security Безопасность Select a category for which you want to see or modify the settings. Выберите категорию для просмотра и модификации настроек. &OK &Принять Alt+O Alt+O Accept and save your changes. Принять и сохранить изменения. &Cancel &Отмена Alt+C Alt+C Undo all your changes and close the window. Отменить все измения и закрыть окно. SIP account Учётная запись SIP &User name*: &Имя пользователя*: &Domain*: &Домен*: Or&ganization: Ор&ганизация: The SIP user name given to you by your provider. It is the user part in your SIP address, <b>username</b>@domain.com This could be a telephone number. <br><br> This field is mandatory. SIP имя пользователя, предоставленное вашим провайдером. Пользовательская часть вашего SIP адреса, <b>username</b>@domain.com может быть вашим телефонным номером. <br><br> Это поле является обязательным. The domain part of your SIP address, username@<b>domain.com</b>. Instead of a real domain this could also be the hostname or IP address of your <b>SIP proxy</b>. If you want direct IP phone to IP phone communications then you fill in the hostname or IP address of your computer. <br><br> This field is mandatory. Доменная часть вашего SIP адреса, username@<b>domain.com</b>. Вместо реального домена также может быть именем машины или IP адресом вашего <b>SIP proxy</b>. Если вы звоните напрямую с компьютера на компьютер, то заполните именем или IP адресом вашей машины <br><br> Это поле является обязательным. You may fill in the name of your organization. When you make a call, this might be shown to the called party. Вы можете заполнить имя вашей организации. Когда вы будете осуществлять звонок, оно может быть показано удалённой стороне. This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. Это просто ваше полное имя, например, John Doe. Оно используется в качестве отображаемого имени. Когда Вы делаете звонок, это имя может быть указано в качестве вызывающей стороны. &Your name: &Ваше имя: SIP authentication SIP аутентификация &Realm: &Область: Authentication &name: Аутентификационное &имя: &Password: &Пароль: The realm for authentication. This value must be provided by your SIP provider. If you leave this field empty, then Twinkle will try the user name and password for any realm that it will be challenged with. Область аутентификации. Значение должно быть предоставлено вашим SIP провайдером. Если вы оставите это поле пустым, то Twinkle будет использовать имя пользователя и пароль для любой области, которая требует авторизации. Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Ваше аутентификационное имя для SIP. В большинстве случаем совпадает с именем пользователя SIP. Вы можете ввести здесь другое имя при необходимости. Your password for authentication. Ваш пароль для аутентификации. Registrar Регистратор &Registrar: &Регистратор: The hostname, domain name or IP address of your registrar. If you use an outbound proxy that is the same as your registrar, then you may leave this field empty and only fill in the address of the outbound proxy. Имя компьютера, доменное имя или IP адрес вашего регистратора. Если вы используете исходящий прокси, совпадающий с вашим регистратором, вы можете оставить его пустым. &Expiry: &Устаревание: The registration expiry time that Twinkle will request. Время устаревание регистрации, после которого Twinkle перепошлёт запрос заново. seconds секунд Re&gister at startup Ре&гистрироваться при запуске Alt+G Alt+G Indicates if Twinkle should automatically register when you run this user profile. You should disable this when you want to do direct IP phone to IP phone communication without a SIP proxy. Указывает, должен ли Twinkle автоматически регистрироваться, когда запускается профиль пользователя. Если отключить эту опцию, то соединения будут происходить напрямую между IP клиентами без обращения к SIP прокси. Add q-value to registration Добавить q-значение к регистрации The q-value indicates the priority of your registered device. If besides Twinkle you register other SIP devices for this account, then the network may use these values to determine which device to try first when delivering a call. Q-значение указывает приоритет вашего зарегистриванного устройства. Если кроме Twinkle вы регистрируете эту учётную запись на другом SIP устройстве, то сеть может определять по этому значению какому устройству перенаправить вызов в первую очередь. The q-value is a value between 0.000 and 1.000. A higher value means a higher priority. Q-значение может изменятся в пределах между 0.000 и 1.000. Большее значение равно большему приоритету (для примера 1 > 0.1 > 0.01). Outbound Proxy Исходящий прокси &Use outbound proxy &Использовать исходящий прокси Alt+U Alt+U Indicates if Twinkle should use an outbound proxy. If an outbound proxy is used then all SIP requests are sent to this proxy. Without an outbound proxy, Twinkle will try to resolve the SIP address that you type for a call invitation for example to an IP address and send the SIP request there. Outbound &proxy: Исходящий &прокси: &Send in-dialog requests to proxy &Посылать запросы к прокси Alt+S Alt+S SIP requests within a SIP dialog are normally sent to the address in the contact-headers exchanged during call setup. If you tick this box, that address is ignored and in-dialog request are also sent to the outbound proxy. &Don't send a request to proxy if its destination can be resolved locally. &Не посылать запрос к прокси если назначение может быть определено локально. Alt+D Alt+D When you tick this option Twinkle will first try to resolve a SIP address to an IP address itself. If it can, then the SIP request will be sent there. Only when it cannot resolve the address, it will send the SIP request to the proxy (note that an in-dialog request will only be sent to the proxy in this case when you also ticked the previous option.) Если вы отметите эту опцию, Twinkle сначала попытается определить SIP адрес как IP адрес. Если сможет, то SIP запрос будет послан напрямую. Только если он не сможет определить адрес, Twinkle пошлёт SIP запрос на прокси (замечание: запрос будет послан только в том случае, если вы также выбрали предыдущий параметр.) The hostname, domain name or IP address of your outbound proxy. Имя сервера, доменное имя или IP адрес вашего исходящего прокси. Co&decs Ко&деки Codecs Кодеки Available codecs: Доступные кодеки: G.711 A-law G.711 A-law G.711 u-law G.711 u-law GSM GSM speex-nb (8 kHz) speex-nb (8 кГц) speex-wb (16 kHz) speex-wb (16 кГц) speex-uwb (32 kHz) speex-uwb (32 кГц) List of available codecs. Список доступных кодеков. Move a codec from the list of available codecs to the list of active codecs. Переместите кодек из списка доступных кодеков в список активных кодеков. Move a codec from the list of active codecs to the list of available codecs. Переместите кодек из списка активных кодеков в список доступных кодеков. Active codecs: Активные кодеки: List of active codecs. These are the codecs that will be used for media negotiation during call setup. The order of the codecs is the order of preference of use. Список активных кодеков. Эти кодеки будут использовании при согласовании медиа потоков при установке соединения. Очерёдность кодеков - это приоритетность их использования. Move a codec upwards in the list of active codecs, i.e. increase its preference of use. Перемещение кодеков вверх по списку увеличивает их приоритет при использовании. Move a codec downwards in the list of active codecs, i.e. decrease its preference of use. Перемещение кодеков вниз по списку понижает их приоритет при использовании. &G.711/G.726 payload size: &G.711/G.726 payload размер: The preferred payload size for the G.711 and G.726 codecs. Приоритетный payload размер для G.711 и G.726 кодеков. ms мс &Follow codec preference from far end on incoming calls &Следовать настройкам кодеков удалённой стороны при входящем звонке Alt+F Alt+F <p> For incoming calls, follow the preference from the far-end (SDP offer). Pick the first codec from the SDP offer that is also in the list of active codecs. <p> If you disable this option, then the first codec from the active codecs that is also in the SDP offer is picked. Follow codec &preference from far end on outgoing calls Следовать &настройкам кодеков удалённой стороны при исходящем звонке Alt+P Alt+P <p> For outgoing calls, follow the preference from the far-end (SDP answer). Pick the first codec from the SDP answer that is also in the list of active codecs. <p> If you disable this option, then the first codec from the active codecs that is also in the SDP answer is picked. &iLBC &iLBC iLBC iLBC i&LBC payload type: i&LBC payload тип: iLBC &payload size (ms): iLBC &payload размер (мс): The dynamic type value (96 or higher) to be used for iLBC. Значение динамического типа (96 или выше) чтобы использовать для iLBC. 20 20 30 30 The preferred payload size for iLBC. Приоритетный payload размер для iLBC. &Speex &Speex Speex Speex Perceptual &enhancement &Улучшенное восприятие Alt+E Alt+E Perceptual enhancement is a part of the decoder which, when turned on, tries to reduce (the perception of) the noise produced by the coding/decoding process. In most cases, perceptual enhancement make the sound further from the original objectively (if you use SNR), but in the end it still sounds better (subjective improvement). &Ultra wide band payload type: &Крайний широкополосный payload тип: Alt+V Alt+V &Wide band payload type: &Широкополосный payload тип: Alt+B Alt+B Variable bit-rate (VBR) allows a codec to change its bit-rate dynamically to adapt to the "difficulty" of the audio being encoded. In the example of Speex, sounds like vowels and high-energy transients require a higher bit-rate to achieve good quality, while fricatives (e.g. s,f sounds) can be coded adequately with less bits. For this reason, VBR can achieve a lower bit-rate for the same quality, or a better quality for a certain bit-rate. Despite its advantages, VBR has two main drawbacks: first, by only specifying quality, there's no guarantee about the final average bit-rate. Second, for some real-time applications like voice over IP (VoIP), what counts is the maximum bit-rate, which must be low enough for the communication channel. The dynamic type value (96 or higher) to be used for speex wide band. Значение динамического типа (96 или выше) чтобы использовать для широкополосного speex. Co&mplexity: &Сложность: Discontinuous transmission is an addition to VAD/VBR operation, that allows one to stop transmitting completely when the background noise is stationary. The dynamic type value (96 or higher) to be used for speex narrow band. Значение динамического типа (96 или выше) чтобы использовать для узкополосного speex. With Speex, it is possible to vary the complexity allowed for the encoder. This is done by controlling how the search is performed with an integer ranging from 1 to 10 in a way that's similar to the -1 to -9 options to gzip and bzip2 compression utilities. For normal use, the noise level at complexity 1 is between 1 and 2 dB higher than at complexity 10, but the CPU requirements for complexity 10 is about 5 times higher than for complexity 1. In practice, the best trade-off is between complexity 2 and 4, though higher settings are often useful when encoding non-speech sounds like DTMF tones. &Narrow band payload type: &Узкополосный payload тип: G.726 G.726 G.726 &40 kbps payload type: G.726 &40 кбит payload тип: The dynamic type value (96 or higher) to be used for G.726 40 kbps. Значение динамического типа (96 или выше) чтобы использовать для G.726 40 кбит. The dynamic type value (96 or higher) to be used for G.726 32 kbps. Значение динамического типа (96 или выше) чтобы использовать для G.726 32 кбит. G.726 &24 kbps payload type: G.726 &24 кбит payload тип: The dynamic type value (96 or higher) to be used for G.726 24 kbps. Значение динамического типа (96 или выше) чтобы использовать для G.726 24 кбит. G.726 &32 kbps payload type: G.726 &32 кбит payload тип: The dynamic type value (96 or higher) to be used for G.726 16 kbps. Значение динамического типа (96 или выше) чтобы использовать для G.726 16 кбит. G.726 &16 kbps payload type: G.726 &16 кбит payload тип: Codeword &packing order: &Кодовое слово порядка опакечивания: RFC 3551 RFC 3551 ATM AAL2 ATM AAL2 There are 2 standards to pack the G.726 codewords into an RTP packet. RFC 3551 is the default packing method. Some SIP devices use ATM AAL2 however. If you experience bad quality using G.726 with RFC 3551 packing, then try ATM AAL2 packing. DT&MF DT&MF DTMF DTMF The dynamic type value (96 or higher) to be used for DTMF events (RFC 2833). Значение динамического типа (96 или выше) чтобы использовать для событий DTMF (RFC 2833). DTMF vo&lume: DTMF ур&овень: The power level of the DTMF tone in dB. Уровень мощности тона DTMF в дБ. The pause after a DTMF tone. Пауза после DTMF тона. DTMF &duration: DTMF &длина: DTMF payload &type: DTMF payload &тип: DTMF &pause: DTMF &пауза: dB дБ Duration of a DTMF tone. Продолжительность посылки DTMF тона. DTMF t&ransport: DTMF т&ранспорт: Auto Авто RFC 2833 RFC 2833 Inband Внутриполосный Out-of-band (SIP INFO) Внеполосный (SIP INFO) <h2>RFC 2833</h2> <p>Send DTMF tones as RFC 2833 telephone events.</p> <h2>Inband</h2> <p>Send DTMF inband.</p> <h2>Auto</h2> <p>If the far end of your call supports RFC 2833, then a DTMF tone will be send as RFC 2833 telephone event, otherwise it will be sent inband. </p> <h2>Out-of-band (SIP INFO)</h2> <p> Send DTMF out-of-band via a SIP INFO request. </p> General Основные Protocol options Опции протокола Call &Hold variant: &Вариант удержания вызова: RFC 2543 RFC 2543 RFC 3264 RFC 3264 Indicates if RFC 2543 (set media IP address in SDP to 0.0.0.0) or RFC 3264 (use direction attributes in SDP) is used to put a call on-hold. Allow m&issing Contact header in 200 OK on REGISTER &Позволить отсутствие заголовка контакта в 200 OK для регистрации Alt+I Alt+I A 200 OK response on a REGISTER request must contain a Contact header. Some registrars however, do not include a Contact header or include a wrong Contact header. This option allows for such a deviation from the specs. &Max-Forwards header is mandatory Заголовок &Max-Forwards обязателен Alt+M Alt+M According to RFC 3261 the Max-Forwards header is mandatory. But many implementations do not send this header. If you tick this box, Twinkle will reject a SIP request if Max-Forwards is missing. Put &registration expiry time in contact header &Добавлять время окончания регистрации в заголовок контакта Alt+R Alt+R In a REGISTER message the expiry time for registration can be put in the Contact header or in the Expires header. If you tick this box it will be put in the Contact header, otherwise it goes in the Expires header. &Use compact header names &Использовать компактные имена заголовков Indicates if compact header names should be used for headers that have a compact form. Allow SDP change during call setup Разрешать изменения SDP при установке вызова <p>A SIP UAS may send SDP in a 1XX response for early media, e.g. ringing tone. When the call is answered the SIP UAS should send the same SDP in the 200 OK response according to RFC 3261. Once SDP has been received, SDP in subsequent responses should be discarded.</p> <p>By allowing SDP to change during call setup, Twinkle will not discard SDP in subsequent responses and modify the media stream if the SDP is changed. When the SDP in a response is changed, it must have a new version number in the o= line.</p> Use domain &name to create a unique contact header value Используйте доменное &имя для создания значения уникального заголовка контакта Alt+N Alt+N <p> Twinkle creates a unique contact header value by combining the SIP user name and domain: </p> <p> <tt>&nbsp;user_domain@local_ip</tt> </p> <p> This way 2 user profiles, having the same user name but different domain names, have unique contact addresses and hence can be activated simultaneously. </p> <p> Some proxies do not handle a contact header value like this. You can disable this option to get a contact header value like this: </p> <p> <tt>&nbsp;user@local_ip</tt> </p> <p> This format is what most SIP phones use. </p> &Encode Via, Route, Record-Route as list &Кодировать канал, маршрут, запись маршрута как список The Via, Route and Record-Route headers can be encoded as a list of comma separated values or as multiple occurrences of the same header. Redirection Перенаправление &Allow redirection &Разрешить перенаправление Alt+A Alt+A Indicates if Twinkle should redirect a request if a 3XX response is received. Ask user &permission to redirect &Спрашивать права пользователя для перенаправления Indicates if Twinkle should ask the user before redirecting a request when a 3XX response is received. Max re&directions: &Максимум перенаправлений: The number of redirect addresses that Twinkle tries at a maximum before it gives up redirecting a request. This prevents a request from getting redirected forever. SIP extensions Расширения SIP disabled отключен supported поддержан required необходим preferred предпочтён Indicates if the 100rel extension (PRACK) is supported:<br><br> <b>disabled</b>: 100rel extension is disabled <br><br> <b>supported</b>: 100rel is supported (it is added in the supported header of an outgoing INVITE). A far-end can now require a PRACK on a 1xx response. <br><br> <b>required</b>: 100rel is required (it is put in the require header of an outgoing INVITE). If an incoming INVITE indicates that it supports 100rel, then Twinkle will require a PRACK when sending a 1xx response. A call will fail when the far-end does not support 100rel. <br><br> <b>preferred</b>: Similar to required, but if a call fails because the far-end indicates it does not support 100rel (420 response) then the call will be re-attempted without the 100rel requirement. &100 rel (PRACK): &100 выпусков (PRACK): Replaces Замены Indicates if the Replaces-extenstion is supported. REFER Рефер Call transfer (REFER) Перевод звонка (рефер) Alt+T Alt+T Indicates if Twinkle should transfer a call if a REFER request is received. As&k user permission to transfer С&прашивать права пользователя для передачи Alt+K Alt+K Indicates if Twinkle should ask the user before transferring a call when a REFER request is received. Hold call &with referrer while setting up call to transfer target &Удерживать звонок с посредником при настройке звонка в цель передачи Alt+W Alt+W Indicates if Twinkle should put the current call on hold when a REFER request to transfer a call is received. Ho&ld call with referee before sending REFER &Удерживать вызов с посредником перед отправкой рефера Alt+L Alt+L Indicates if Twinkle should put the current call on hold when you transfer a call. Auto re&fresh subscription to refer event while call transfer is not finished &Автоматически обновлять подписку на событие рефера пока перевод звонка не окончен While a call is being transferred, the referee sends NOTIFY messages to the referrer about the progress of the transfer. These messages are only sent for a short interval which length is determined by the referee. If you tick this box, the referrer will automatically send a SUBSCRIBE to lengthen this interval if it is about to expire and the transfer has not yet been completed. Attended refer to AoR (Address of Record) Добавлять рефера в AoR (адрес записи) An attended call transfer should use the contact URI as a refer target. A contact URI may not be globally routable however. Alternatively the AoR (Address of Record) may be used. A disadvantage is that the AoR may route to multiple endpoints in case of forking whereas the contact URI routes to a single endoint. Privacy Защита Privacy options Опции защиты &Send P-Preferred-Identity header when hiding user identity &Отправлять заголовок P-Preferred-Identity когда личность пользователя скрывается Include a P-Preferred-Identity header with your identity in an INVITE request for a call with identity hiding. SIP transport Транспорт SIP UDP UDP TCP TCP Transport mode for SIP. In auto mode, the size of a message determines which transport protocol is used. Messages larger than the UDP threshold are sent via TCP. Smaller messages are sent via UDP. T&ransport protocol: Протокол т&ранспорта: UDP t&hreshold: &Порог UDP: Messages larger than the threshold are sent via TCP. Smaller messages are sent via UDP. NAT traversal Пересечение NAT &NAT traversal not needed Пересечение &NAT не нужно Choose this option when there is no NAT device between you and your SIP proxy or when your SIP provider offers hosted NAT traversal. &Use statically configured public IP address inside SIP messages &Использовать статически настроенный публичный IP-адрес внутри SIP сообщений Indicates if Twinkle should use the public IP address specified in the next field inside SIP message, i.e. in SIP headers and SDP body instead of the IP address of your network interface.<br><br> When you choose this option you have to create static address mappings in your NAT device as well. You have to map the RTP ports on the public IP address to the same ports on the private IP address of your PC. Use &STUN (does not work for incoming TCP) Использовать &STUN (не работает для входящего TCP) Choose this option when your SIP provider offers a STUN server for NAT traversal. Выберите эту опцию если ваш провайдер SIP предлагает STUN-сервер для обхода NAT. S&TUN server: S&TUN сервер: The hostname, domain name or IP address of the STUN server. Имя сервера, доменное имя или IP адрес STUN сервера. &Public IP address: &Публичный IP-адрес: The public IP address of your NAT. Публичный IP-адрес вашего NAT. Telephone numbers Номера телефонов Only &display user part of URI for telephone number &Отображать только пользовательскую часть URI для телефонного номера If a URI indicates a telephone number, then only display the user part. E.g. if a call comes in from sip:123456@twinklephone.com then display only "123456" to the user. A URI indicates a telephone number if it contains the "user=phone" parameter or when it has a numerical user part and you ticked the next option. &URI with numerical user part is a telephone number Телефонный номер - это &URI с числовой пользовательской частью If you tick this option, then Twinkle considers a SIP address that has a user part that consists of digits, *, #, + and special symbols only as a telephone number. In an outgoing message, Twinkle will add the "user=phone" parameter to such a URI. &Remove special symbols from numerical dial strings &Удалять спецсимволы из строк числового набора Telephone numbers are often written with special symbols like dashes and brackets to make them readable to humans. When you dial such a number the special symbols must not be dialed. To allow you to simply copy/paste such a number into Twinkle, Twinkle can remove these symbols when you hit the dial button. &Special symbols: &Спецсимволы: The special symbols that may be part of a telephone number for nice formatting, but must be removed when dialing. Специальные символы, которые могут быть частью телефонного номера для красивого форматирования, но должны быть удалены при наборе номера. Number conversion Преобразователь номеров Match expression Совпадение Replace Замена <p> Often the format of the telphone numbers you need to dial is different from the format of the telephone numbers stored in your address book, e.g. your numbers start with a +-symbol followed by a country code, but your provider expects '00' instead of the '+', or you are at the office and all your numbers need to be prefixed with a '9' to access an outside line. Here you can specify number format conversion using Perl style regular expressions and format strings. </p> <p> For each number you dial, Twinkle will try to find a match in the list of match expressions. For the first match it finds, the number will be replaced with the format string. If no match is found, the number stays unchanged. </p> <p> The number conversion rules are also applied to incoming calls, so the numbers are displayed in the format you want. </p> <h3>Example 1</h3> <p> Assume your country code is 31 and you have stored all numbers in your address book in full international number format, e.g. +318712345678. For dialling numbers in your own country you want to strip of the '+31' and replace it by a '0'. For dialling numbers abroad you just want to replace the '+' by '00'. </p> <p> The following rules will do the trick: </p> <blockquote> <tt> Match expression = \+31([0-9]*) , Replace = 0$1<br> Match expression = \+([0-9]*) , Replace = 00$1</br> </tt> </blockquote> <h3>Example 2</h3> <p> You are at work and all telephone numbers starting with a 0 should be prefixed with a 9 for an outside line. </p> <blockquote> <tt> Match expression = 0[0-9]* , Replace = 9$&<br> </tt> </blockquote> Move the selected number conversion rule upwards in the list. Переместить выбранное правило преобразования номера вверх в списке. Move the selected number conversion rule downwards in the list. Переместить выбранное правило преобразования номера вниз в списке. &Add &Добавить Add a number conversion rule. Добавить правило преобразования номера. Re&move &Удалить Remove the selected number conversion rule. Удалить выбранное правило преобразования номера. &Edit &Редактировать Edit the selected number conversion rule. Редактировать выбранное правило преобразования номера. Type a telephone number here an press the Test button to see how it is converted by the list of number conversion rules. Напечатайте телефонный номер здесь и нажмите кнопку Теста чтобы увидеть, как он был преобразован используя список правил преобразования номера. &Test &Тест Test how a number is converted by the number conversion rules. Протестировать как номер был преобразован используя правила преобразования номера. When an incoming call is received, this timer is started. If the user answers the call, the timer is stopped. If the timer expires before the user answers the call, then Twinkle will reject the call with a "480 User Not Responding". NAT &keep alive: Охранять NAT д&ействующим: &No answer: &Нет ответа: Select ring back tone file. Выберите файл гудка вызова. Select ring tone file. Выберите файл сигнала вызова. Ring &back tone: Гудок &вызова: <p> Specify the file name of a .wav file that you want to be played as ring back tone for this user. </p> <p> This ring back tone overrides the ring back tone settings in the system settings. </p> <p> Specify the file name of a .wav file that you want to be played as ring tone for this user. </p> <p> This ring tone overrides the ring tone settings in the system settings. </p> &Ring tone: &Сигнал вызова: <p> This script is called when you release a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing SIP BYE request are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=local_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the BYE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. Select script file. Выберите файл скрипта. <p> This script is called when an incoming call fails. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing SIP failure response are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=in_call_failed</b>. <b>SIPSTATUS_CODE</b> contains the status code of the failure response. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> This script is called when the remote party releases a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming SIP BYE request are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=remote_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the BYE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> You can customize the way Twinkle handles incoming calls. Twinkle can call a script when a call comes in. Based on the output of the script Twinkle accepts, rejects or redirects the call. When accepting the call, the ring tone can be customized by the script as well. The script can be any executable program. </p> <p> <b>Note:</b> Twinkle pauses while your script runs. It is recommended that your script does not take more than 200 ms. When you need more time, you can send the parameters followed by <b>end</b> and keep on running. Twinkle will continue when it receives the <b>end</b> parameter. </p> <p> With your script you can customize call handling by outputting one or more of the following parameters to stdout. Each parameter should be on a separate line. </p> <p> <blockquote> <tt> action=[ continue | reject | dnd | redirect | autoanswer ]<br> reason=&lt;string&gt;<br> contact=&lt;address to redirect to&gt;<br> caller_name=&lt;name of caller to display&gt;<br> ringtone=&lt;file name of .wav file&gt;<br> display_msg=&lt;message to show on display&gt;<br> end<br> </tt> </blockquote> </p> <h2>Parameters</h2> <h3>action</h3> <p> <b>continue</b> - continue call handling as usual<br> <b>reject</b> - reject call<br> <b>dnd</b> - deny call with do not disturb indication<br> <b>redirect</b> - redirect call to address specified by <b>contact</b><br> <b>autoanswer</b> - automatically answer a call<br> </p> <p> When the script does not write an action to stdout, then the default action is continue. </p> <p> <b>reason: </b> With the reason parameter you can set the reason string for reject or dnd. This might be shown to the far-end user. </p> <p> <b>caller_name: </b> This parameter will override the display name of the caller. </p> <p> <b>ringtone: </b> The ringtone parameter specifies the .wav file that will be played as ring tone when action is continue. </p> <h2>Environment variables</h2> <p> The values of all SIP headers in the incoming INVITE message are passed in environment variables to your script. The variable names are formatted as <b>SIP_&lt;HEADER_NAME&gt;</b> E.g. SIP_FROM contains the value of the from header. </p> <p> TWINKLE_TRIGGER=in_call. SIPREQUEST_METHOD=INVITE. The request-URI of the INVITE will be passed in <b>SIPREQUEST_URI</b>. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> This script is called when the remote party answers your call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming 200 OK are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> This script is called when you answer an incoming call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing 200 OK are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=in_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. Call released locall&y: Вызов про&изошёл локально: <p> This script is called when an outgoing call fails. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming SIP failure response are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call_failed</b>. <b>SIPSTATUS_CODE</b> contains the status code of the failure response. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> This script is called when you make a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing INVITE are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call</b>. <b>SIPREQUEST_METHOD=INVITE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the INVITE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. Outgoing call a&nswered: Исход&ящий звонок отвечен: Incoming call &failed: Входя&щий звонок неудачен: &Incoming call: &Входящий звонок: Call released &remotely: Вызов прои&зошёл удалённо: Incoming call &answered: В&ходящий звонок отвечен: O&utgoing call: &Исходящий звонок: Out&going call failed: И&сходящий звонок неудачен: &Enable ZRTP/SRTP encryption &Включить ZRTP/SRTP шифрование When ZRTP/SRTP is enabled, then Twinkle will try to encrypt the audio of each call you originate or receive. Encryption will only succeed if the remote party has ZRTP/SRTP support enabled. If the remote party does not support ZRTP/SRTP, then the audio channel will stay unecrypted. ZRTP settings ZRTP настройки O&nly encrypt audio if remote party indicated ZRTP support in SDP &Только шифровать аудио если удалённая сторона свидетельствует о поддержке ZRTP в SDP A SIP endpoint supporting ZRTP may indicate ZRTP support during call setup in its signalling. Enabling this option will cause Twinkle only to encrypt calls when the remote party indicates ZRTP support. &Indicate ZRTP support in SDP &Определять поддержку SRTP в SDP Twinkle will indicate ZRTP support during call setup in its signalling. &Popup warning when remote party disables encryption during call &Всплывающее предупреждение когда удалённая сторона отключает шифрование во время вызова A remote party of an encrypted call may send a ZRTP go-clear command to stop encryption. When Twinkle receives this command it will popup a warning if this option is enabled. &Voice mail address: &Адрес голосовой почты: The SIP address or telephone number to access your voice mail. SIP адрес или телефонный номер для доступа к вашей голосовой почте. Unsollicited Незапрошен Sollicited Запрошен <H2>Message waiting indication type</H2> <p> If your provider offers the message waiting indication service, then Twinkle can show you when new voice mail messages are waiting. Ask your provider which type of message waiting indication is offered. </p> <H3>Unsollicited</H3> <p> Asterisk provides unsollicited message waiting indication. </p> <H3>Sollicited</H3> <p> Sollicited message waiting indication as specified by RFC 3842. </p> &MWI type: &MWI тип: Sollicited MWI Запрошенный MWI Subscription &duration: Продолжительность &подписки: Mailbox &user name: Почтовый &ящик: The hostname, domain name or IP address of your voice mailbox server. Имя сервера, доменное имя или IP адрес вашего сервера голосовой почты. For sollicited MWI, an endpoint subscribes to the message status for a limited duration. Just before the duration expires, the endpoint should refresh the subscription. Your user name for accessing your voice mailbox. Ваше имя пользователя для доступа к вашей голосовой почте. Mailbox &server: Почтовый &сервер: Via outbound &proxy Через исходящий &прокси Check this option if Twinkle should send SIP messages to the mailbox server via the outbound proxy. &Maximum number of sessions: &Максимальное число сессий: When you have this number of instant message sessions open, new incoming message sessions will be rejected. Это число ограничивает количество открытых сеансов мгновенных сообщений, при превышении этого значения новые сессии будут отвергнуты. Your presence Ваша доступность &Publish availability at startup &Публиковать доступность при запуске Publish your availability at startup. Публикует вашу доступность при запуске. Publication &refresh interval (sec): Интервал &обновления публикации (сек): Refresh rate of presence publications. Частота обновлений публикации доступности. Buddy presence Доступность друзей &Subscription refresh interval (sec): Интервал обновления &подписки (сек): Refresh rate of presence subscriptions. Частота обновлений подписки. Dynamic payload type %1 is used more than once. Динамический payload тип %1 использован более, чем один раз. You must fill in a user name for your SIP account. Вы должны заполнить имя пользователя для вашей учётной записи SIP. You must fill in a domain name for your SIP account. This could be the hostname or IP address of your PC if you want direct PC to PC dialing. Вы должны заполнить доменное имя для вашей учётной записи SIP. Это может быть имя сервера или IP адрес вашего компьютера при прямых звонках. Invalid domain. Неправильный домен. Invalid user name. Неправильное имя пользователя. Invalid value for registrar. Неправильное значение для регистратора. Invalid value for outbound proxy. Неправильное значение для исходящего прокси. You must fill in a mailbox user name. Вы должны заполнить имя пользователя почтового ящика. You must fill in a mailbox server Вы должны заполнить сервер почтового ящика. Invalid mailbox server. Неверный почтовый сервер. Invalid mailbox user name. Неправильное имя пользователя для почтового ящика. Value for public IP address missing. Значение для внешнего IP адреса отсутствует. Invalid value for STUN server. Неправильное значение для STUN сервера. Ring tones Description of .wav files in file dialog Сигналы вызова Choose ring tone Выберите сигнал вызова Ring back tones Description of .wav files in file dialog Гудок вызова All files Все файлы Choose incoming call script Выберите скрипт входящего звонка Choose incoming call answered script Выберите скрипт входящего отвеченного звонка Choose incoming call failed script Выберите скрипт входящего звонка с ошибкой Choose outgoing call script Выберите скрипт исходящего звонка Choose outgoing call answered script Выберите скрипт исходящего отвеченного звонка Choose outgoing call failed script Выберите скрипт исходящего звонка с ошибкой Choose local release script Выберите локальный скрипт Choose remote release script Выберите удалённый скрипт %1 converts to %2 %1 конвертирован в %2 P&ersistent TCP connection П&остоянные TCP соединения Keep the TCP connection established during registration open such that the SIP proxy can reuse this connection to send incoming requests. Application ping packets are sent to test if the connection is still alive. &Send composing indications when typing a message. &Посылать оповещение о наборе текста при вводе сообщения. Twinkle sends a composing indication when you type a message. This way the recipient can see that you are typing. Twinkle посылает оповещения при наборе сообщения. Получатель будет видеть когда вы печатате. AKA AM&F: AKA AM&F: A&KA OP: A&KA OP: Authentication management field for AKAv1-MD5 authentication. Operator variant key for AKAv1-MD5 authentication. Prepr&ocessing Пред&обработка Preprocessing (improves quality at remote end) Предварительная обработка (улучшает качество звука на удалённой стороне) &Automatic gain control &Автоматический контроль усиления Automatic gain control (AGC) is a feature that deals with the fact that the recording volume may vary by a large amount between different setups. The AGC provides a way to adjust a signal to a reference volume. This is useful because it removes the need for manual adjustment of the microphone gain. A secondary advantage is that by setting the microphone gain to a conservative (low) level, it is easier to avoid clipping. Automatic gain control &level: Автоматический контроль уровня &усиления: Automatic gain control level represents percentual value of automatic gain setting of a microphone. Recommended value is about 25%. &Voice activity detection &Определение голосовой активности When enabled, voice activity detection detects whether the input signal represents a speech or a silence/background noise. &Noise reduction &Понижение шума The noise reduction can be used to reduce the amount of background noise present in the input signal. This provides higher quality speech. Acoustic &Echo Cancellation &Отмена акустического эха In any VoIP communication, if a speech from the remote end is played in the local loudspeaker, then it propagates in the room and is captured by the microphone. If the audio captured from the microphone is sent directly to the remote end, then the remote user hears an echo of his voice. An acoustic echo cancellation is designed to remove the acoustic echo before it is sent to the remote end. It is important to understand that the echo canceller is meant to improve the quality on the remote end. Variable &bit-rate Переменный &битрейт Discontinuous &Transmission &Прерывистая передача &Quality: &Качество: Speex is a lossy codec, which means that it achives compression at the expense of fidelity of the input speech signal. Unlike some other speech codecs, it is possible to control the tradeoff made between quality and bit-rate. The Speex encoding process is controlled most of the time by a quality parameter that ranges from 0 to 10. bytes байт Use tel-URI for telephone &number Использовать tel-URI для телефонного &номера Expand a dialed telephone number to a tel-URI instead of a sip-URI. Accept call &transfer request (incoming REFER) &Принимать запрос переадресации звонков (входящий рефер) Allow call transfer while consultation in progress Разрешать перевод звонка когда идёт консультация When you perform an attended call transfer, you normally transfer the call after you established a consultation call. If you enable this option you can transfer the call while the consultation call is still in progress. This is a non-standard implementation and may not work with all SIP devices. Enable NAT &keep alive Включить охрану NAT д&ействующим Send UDP NAT keep alive packets. If you have enabled STUN or NAT keep alive, then Twinkle will send keep alive packets at this interval rate to keep the address bindings in your NAT device alive. WizardForm Twinkle - Wizard Twinkle - Мастер The hostname, domain name or IP address of the STUN server. Имя сервера, доменное имя или IP адрес STUN сервера. S&TUN server: S&TUN сервер: The SIP user name given to you by your provider. It is the user part in your SIP address, <b>username</b>@domain.com This could be a telephone number. <br><br> This field is mandatory. SIP имя пользователя, предоставленное вашим провайдером. Пользовательская часть вашего SIP адреса, <b>username</b>@domain.com может быть вашим телефонным номером. <br><br> Это поле является обязательным. &Domain*: &Домен*: Choose your SIP service provider. If your SIP service provider is not in the list, then select <b>Other</b> and fill in the settings you received from your provider.<br><br> If you select one of the predefined SIP service providers then you only have to fill in your name, user name, authentication name and password. Выберите вашего провайдера SIP услуг. Если вашего провайдера нет в списке, тогда выберите <b>Другой</b> и заполните поля данными полученными от вашего провайдера.<br><br> Если вы выбрали одного из предопределённых провайдеров SIP услуг, то вы должны только заполнить ваше имя, имя пользователя, имя аутентификации и пароль. &Authentication name: &Аутентификационное имя: &Your name: &Ваше имя: Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Ваше имя SIP аутентификации. Довольно часто совпадает с именем пользователя SIP. Однако может иметь и другое значение. The domain part of your SIP address, username@<b>domain.com</b>. Instead of a real domain this could also be the hostname or IP address of your <b>SIP proxy</b>. If you want direct IP phone to IP phone communications then you fill in the hostname or IP address of your computer. <br><br> This field is mandatory. Доменная часть вашего SIP адреса , username@<b>domain.com</b>. Вместо реального домена также может быть именем машины или IP адресом вашего <b>SIP proxy</b>. Если вы звоните напрямую с компьютера на компьютер, то заполните именем или IP адресом вашей машины <br><br> Это поле является обязательным. This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. Это просто ваше полное имя, например John Doe. Оно используется в качестве отображаемого имени. Когда Вы делаете звонок, это имя может быть указано в качестве вызывающей стороны. SIP pro&xy: SIP про&кси: The hostname, domain name or IP address of your SIP proxy. If this is the same value as your domain, you may leave this field empty. Имя компьютера, доменное имя или IP адрес вашего SIP прокси. Если оно совпадает со значением вашего домена, вы можете оставить его пустым. &SIP service provider: Провайдер &SIP услуг: &Password: &Пароль: &User name*: &Имя пользователя*: Your password for authentication. Ваш пароль для аутентификации. &OK &Принять Alt+O Alt+O &Cancel &Отмена Alt+C Alt+C None (direct IP to IP calls) Отсутствует (Прямое IP - IP соединение) Other Другой User profile wizard: Помощник профиля пользователя: You must fill in a user name for your SIP account. Вы должны заполнить имя пользователя для вашей учётной записи SIP. You must fill in a domain name for your SIP account. This could be the hostname or IP address of your PC if you want direct PC to PC dialing. Вы должны заполнить имя домена для вашей учётной записи SIP. Это может быть имя или IP адрес вашего компьютера если вы выполняете прямые звонки между компьютерами. Invalid value for SIP proxy. Неправильное значение для SIP прокси. Invalid value for STUN server. Неправильное значение для STUN сервера. YesNoDialog &Yes &Да &No &Нет incoming_call Answer Ответить Reject Отклонить twinkle-1.10.1/src/gui/lang/twinkle_sv.ts000066400000000000000000006710011277565361200203230ustar00rootroot00000000000000 AddressCardForm Twinkle - Address Card Twinkle - Adresskort &Remark: Infix name of contact. First name of contact. Förnamn för kontakten. &First name: &Förnamn: You may place any remark about the contact here. &Phone: &Telefon: &Infix name: Phone number or SIP address of contact. Telefonnummer eller SIP-adress för kontakten. Last name of contact. Efternamn för kontakten. &Last name: &Efternamn: &OK &OK Alt+O Alt+O &Cancel &Avbryt Alt+C Alt+A You must fill in a name. Du måste ange ett namn. You must fill in a phone number or SIP address. Du måste ange ett telefonnummer eller SIP-adress. AddressTableModel Name Namn Phone Telefon Remark AuthenticationForm Twinkle - Authentication Twinkle - Autentisering user No need to translate användare The user for which authentication is requested. Användaren för vilken autentisering begärs. profile No need to translate profil The user profile of the user for which authentication is requested. Användarprofilen för användaren för vilken autentisering begärs. User profile: Användarprofil: User: Användare: &Password: &Lösenord: Your password for authentication. Ditt lösenord för autentisering. Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Ditt SIP-autentiseringsnamn. Ganska ofta är detta samma som ditt SIP-användarnamn. Det kan dock vara ett annat namn. &User name: Användar&namn: &OK &OK &Cancel &Avbryt Login required for realm: realm No need to translate The realm for which you need to authenticate. BuddyForm Twinkle - Buddy Twinkle - Kompis Address book Adressbok Select an address from the address book. Välj en adress från adressboken. &Phone: &Telefon: Name of your buddy. Namnet på din kompis. &Show availability &Visa tillgänglighet Alt+S Alt+V Check this option if you want to see the availability of your buddy. This will only work if your provider offers a presence agent. Kryssa för detta alternativ om du vill se tillgängligheten för din kompis. Detta kommer endast att fungera om din leverantör erbjuder en närvaroagent. &Name: &Namn: SIP address your buddy. SIP-adress till din kompis. &OK &OK Alt+O Alt+O &Cancel &Avbryt Alt+C Alt+A You must fill in a name. Du måste ange ett namn. Invalid phone. Ogiltig telefon. Failed to save buddy list: %1 Misslyckades med att spara kompislista: %1 BuddyList Availability Tillgänglighet unknown okänd offline frånkopplad online ansluten request failed begäran misslyckades request rejected begäran avvisades not published inte publicerad failed to publish misslyckades med att publicera Click right to add a buddy. Högerklicka för att lägga till en kompis. CoreAudio Failed to open sound card MIsslyckades med att öppna ljudkortet Failed to create a UDP socket (RTP) on port %1 Misslyckades med att skapa ett UDP-uttag(RTP) på port %1 Failed to create audio receiver thread. Misslyckades med att skapa ljudmottagartråd. Failed to create audio transmitter thread. Misslyckades med att skapa ljudsändartråd. CoreCallHistory local user lokal användare remote user fjärranvändare failure fel unknown okänd in in out ut DeregisterForm Twinkle - Deregister Twinkle - Avregistrering deregister all devices avregistrera alla enheter &OK &OK &Cancel &Avbryt DiamondcardProfileForm This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. Detta är helt enkelt ditt fullständiga namn, t.ex. Sven Svensson. Det används endast för visning. När du ringer ett samtal kan dock detta namn visas för motparten. &Your name: &Ditt namn: &OK &OK &Cancel &Avbryt Fill in your account ID. Fill in your PIN code. A user profile with name %1 already exists. DtmfForm Twinkle - DTMF Twinkle - DTMF Keypad Knappsats 2 2 3 3 Over decadic A. Normally not needed. Över dekadiskt A. Behövs oftast inte. 4 4 5 5 6 6 Over decadic B. Normally not needed. Över dekadiskt B. Behövs oftast inte. 7 7 8 8 9 9 Over decadic C. Normally not needed. Över dekadiskt C. Behövs oftast inte. Star (*) Stjärna (*) 0 0 Pound (#) Fyrkant (#) Over decadic D. Normally not needed. Över dekadiskt D. Behövs oftast inte. 1 1 &Close S&täng Alt+C Alt+T FreeDeskSysTray Show/Hide Visa/Göm Quit Avsluta GUI Failed to create a UDP socket (SIP) on port %1 Kunde inte skapa UDP-socket (SIP) på port %1 Override lock file and start anyway? Strunta i låsfil och starta ändå? The following profiles are both for user %1 Följande profiler är båda för användaren %1 You can only run multiple profiles for different users. Du kan endast köra flera profiler för olika användare. Cannot find a network interface. Twinkle will use 127.0.0.1 as the local IP address. When you connect to the network you have to restart Twinkle to use the correct IP address. Kan inte hitta ett nätverkskort. Twinkle kommer att använda 127.0.0.1 som lokal IP-adress. När du ansluter till nätverket så måste du starta om Twinkle för att använda den korrekta IP-adressen. Line %1: incoming call for %2 Linje %1: inkommande samtal för %2 Call transferred by %1 Samtal kopplat av %1 Line %1: far end cancelled call. Line %1: far end released call. Line %1: SDP answer from far end not supported. Linje %1: SDP-svar från andra parten stöds inte. Line %1: SDP answer from far end missing. Linje %1: Inget SDP-svar från andra parten. Line %1: Unsupported content type in answer from far end. Line %1: no ACK received, call will be terminated. Linje %1: inget ACK mottaget. Samtalet kommer avslutas. Line %1: no PRACK received, call will be terminated. Linje %1: inget PRACK mottaget. Samtalet kommer avslutas. Line %1: PRACK failed. Linje %1: PRACK misslyckades. Line %1: failed to cancel call. Linje %1: misslyckades med att avbryta samtal. Line %1: far end answered call. Linje %1: motparten svarade på samtalet. Line %1: call failed. Linje %1: samtal misslyckades. The call can be redirected to: Samtalet kan vidarekopplas till: Line %1: call released. Line %1: call established. Linje %1: samtal etablerat. Response on terminal capability request: %1 %2 Svar på begäran om terminalförmågor: %1 %2 Terminal capabilities of %1 Terminalförmågor för %1 Accepted body types: unknown okänt Accepted encodings: Accepterade kodningar: Accepted languages: Accepterade språk: Allowed requests: Tillåtna begäran: Supported extensions: none End point type: Typ av motpart: Line %1: call retrieve failed. %1, registration failed: %2 %3 %1, registrering misslyckades: %2 %3 %1, registration succeeded (expires = %2 seconds) %1, registrering lyckades (går ut om = %2 sekunder) %1, registration failed: STUN failure %1, registrering misslyckades: STUN-problem %1, de-registration succeeded: %2 %3 %1, avregistrering lyckades: %2 %3 %1, de-registration failed: %2 %3 %1, avregistrering misslyckades: %2 %3 %1, fetching registrations failed: %2 %3 : you are not registered : du är inte registrerad : you have the following registrations : du har följande registreringar : fetching registrations... : hämtar registreringar... Line %1: redirecting request to Linje %1: Skickar vidare till Redirecting request to: %1 Skickar vidare till: %1 Line %1: DTMF detected: Linje %1: DTMF detekterad: invalid DTMF telephone event (%1) Line %1: send DTMF %2 Linje %1: skicka DTMF %2 Line %1: far end does not support DTMF telephone events. Line %1: received notification. Event: %1 Händelse: %1 State: %1 Tillstånd: %1 Reason: %1 Anledning: %1 Progress: %1 %2 Förlopp: %1 %2 Line %1: call transfer failed. Linje %1: samtalskoppling misslyckades. Line %1: call successfully transferred. Linje %1: samtal kopplades. Line %1: call transfer still in progress. Linje %1: samtalskoppling pågår fortfarande. No further notifications will be received. Line %1: transferring call to %2 Linje %1: kopplar samtal till %2 Transfer requested by %1 Koppilng begärd av %1 Line %1: Call transfer failed. Retrieving original call. %1, STUN request failed: %2 %3 %1. STUN-begäran misslyckades: %2 %3 %1, STUN request failed. %1, STUN-begäran misslyckades. Redirecting call Vidarekoppling av samtal User profile: Användarprofil: User: Användare: Do you allow the call to be redirected to the following destination? Tillåter du samtalet att hänvisas till följande destination? If you don't want to be asked this anymore, then you must change the settings in the SIP protocol section of the user profile. Om du inte vill bli frågad detta igen måste du ändra inställningarna i sektionen SIP-protokoll för användarprofilen. Redirecting request Do you allow the %1 request to be redirected to the following destination? Transferring call Request to transfer call received from: Request to transfer call received. Do you allow the call to be transferred to the following destination? Tillåter du samtalet att kopplas vidare till följande destination? Info: Warning: Varning: Critical: Firewall / NAT discovery... Brandvägg / NAT-identifiering... Abort Avbryt Line %1 Linje %1 Click the padlock to confirm a correct SAS. Klicka på hänglåset för att bekräfta en korrekt SAS. The remote user on line %1 disabled the encryption. Motparten på linje %1 stängde av krypteringen. Line %1: SAS confirmed. Linje %1: SAS bekräftad. Line %1: SAS confirmation reset. %1, voice mail status failure. %1, kan inte hämta status för röstbrevlåda. %1, voice mail status rejected. %1, hämtning av status för röstbrevlåda avvisades. %1, voice mailbox does not exist. %1, röstbrevlådan finns inte. %1, voice mail status terminated. %1, hämtning av status för röstbrevlåda avslutad. Line %1: call rejected. Linje %1: samtal avvisades. Line %1: call redirected. Linje %1: samtal vidarekopplat. Failed to start conference. Kunde inte starta konferens. Failed to create a %1 socket (SIP) on port %2 Misslyckades med att skapa ett %1-uttag (SIP) på port %2 If these are users for different domains, then enable the following option in your user profile (SIP protocol) Use domain name to create a unique contact header Failed to save message attachment: %1 Misslyckades med att spara meddelandebilaga: %1 Accepted by network Accepterad av nätverket Transferred by: %1 Cannot open web browser: %1 Configure your web browser in the system settings. GetAddressForm Twinkle - Select address Twinkle - Välj adress &KAddressBook &KAddressBook Name Namn Type Typ Phone Telefon This list of addresses is taken from <b>KAddressBook</b>. Contacts for which you did not provide a phone number are not shown here. To add, delete or modify address information you have to use KAddressBook. &Show only SIP addresses &Visa endast SIP-adresser Alt+S Alt+V Check this option when you only want to see contacts with SIP addresses, i.e. starting with "<b>sip:</b>". Kryssa för detta alternativ när du endast vill se kontakter med SIP-adresser, alltså börjar med "<b>sip:</b>". &Reload Upp&datera Alt+R Alt+D Reload the list of addresses from KAddressbook. Uppdatera listan över adresser från KAddressbook. &Local address book &Lokal adressbok Contacts in the local address book of Twinkle. Kontakter i den lokala adressboken för Twinkle. &Add &Lägg till Alt+A Alt+L Add a new contact to the local address book. Lägg till en ny kontakt i den lokala adressboken. &Delete &Ta bort Alt+D Alt+T Delete a contact from the local address book. Ta bort en kontakt från den lokala adressboken. &Edit &Redigera Alt+E Alt+R Edit a contact from the local address book. Redigera en kontakt från den lokala adressboken. &OK &OK Alt+O Alt+O &Cancel &Avbryt Alt+C Alt+A <p>You seem not to have any contacts with a phone number in <b>KAddressBook</b>, KDE's address book application. Twinkle retrieves all contacts with a phone number from KAddressBook. To manage your contacts you have to use KAddressBook.<p>As an alternative you may use Twinkle's local address book.</p> GetProfileNameForm Twinkle - Profile name Twinkle - Profilnamn &OK &OK &Cancel &Avbryt Enter a name for your profile: Ge din profil ett namn: <b>The name of your profile</b> <br><br> A profile contains your user settings, e.g. your user name and password. You have to give each profile a name. <br><br> If you have multiple SIP accounts, you can create multiple profiles. When you startup Twinkle it will show you the list of profile names from which you can select the profile you want to run. <br><br> To remember your profiles easily you could use your SIP user name as a profile name, e.g. <b>example@example.com</b> <b>Din profils namn</b> <br><br> En profil innehåller dina användarinställningar. T.ex. ditt användarnamn och lösenord. Du måste ge varje profil ett namn. <br><br> Om du har flera SIP-konton kan du skapa flera profiler. När du startar Twinkle kommer du se en lista över dina profiler och kunna välja vilken av profilerna du vill använda. <br><br> För att enkelt komma ihåg dina profiler kan du använda ditt SIP-användarnamn som profilnamn. T.ex. <b>exempel@exempel.com</b> Cannot find .twinkle directory in your home directory. Kan inte hitta katalogen .twinkle i din hemkatalog. Profile already exists. Profilen finns redan. Rename profile '%1' to: Döp om profilen '%1' till: HistoryForm Twinkle - Call History Twinkle - Samtalshistorik Time Tid In/Out In/Ut From/To Från/Till Subject Ämne Status Status Call details Samtalsdetaljer Details of the selected call record. Detaljer för det valda samtalet. View Visa &Incoming calls &Inkommande samtal Alt+I Alt+I Check this option to show incoming calls. Kryssa i denna ruta för att visa inkommande samtal. &Outgoing calls &Utgående samtal Alt+O Alt+U Check this option to show outgoing calls. Kryssa i denna ruta för att visa utgående samtal. &Answered calls &Besvarade samtal Alt+A Alt+B Check this option to show answered calls. Kryssa i denna ruta för att visa besvarade samtal. &Missed calls &Missade samtal Alt+M Alt+M Check this option to show missed calls. Kryssa i denna ruta för att visa missade samtal. Current &user profiles only Enbart aktuell &användarprofil Alt+U Alt+A Check this option to show only calls associated with this user profile. Kryssa i denna ruta för att enbart visa samtal associerade med aktuell användarprofil. C&lear &Töm Alt+L Alt+T <p>Clear the complete call history.</p> <p><b>Note:</b> this will clear <b>all</b> records, also records not shown depending on the checked view options.</p> <p>Töm hela samtalshistoriken.</p> <p><b>Observera:</b> detta rensar bort <b>alla</b> samtal. Även samtal som inte visas på grund av aktuella visningsalternativ.</p> &Close &Stäng Alt+C Alt+S Close this window. Stäng detta fönster. Call start: Samtal startade: Call answer: Samtal besvarades: Call end: Samtal avslutades: Call duration: Samtalslängd: Direction: Riktning: From: Från: To: Till: Reply to: Svara till: Referred by: Kopplad av: Subject: Ämne: Released by: Avslutat av: Status: Status: Far end device: Enhet på andra sidan: User profile: Användarprofil: conversation konversation Call... Ring upp... Delete Ta bort Re: Sv: Clo&se St&äng Alt+S Alt+Ä &Call &Ring Call selected address. Ring markerad adress. Number of calls: ### Total call duration: IncomingCallPopup %1 calling InviteForm Twinkle - Call Twinkle - Ring &To: &Till: Optionally you can provide a subject here. This might be shown to the callee. Om du vill kan du ange ämne här. Det kan visas för den du ringer. Address book Adressbok Select an address from the address book. Välj en adress från adressboken. The address that you want to call. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Adressen som du vill ringa. Detta kan vara en komplett SIP-adress som <b>sip:exempel@exempel.com</b> eller bara användarnamnet eller telefonnumret i adressen. När du inte anger en komplett adress kommer Twinkle fylla ut adressen med domännamnet från din användarprofil. The user that will make the call. Användare som ringer upp. &Subject: &Ämne: &From: &Från: &Hide identity &Dölj identitet Alt+H Alt+D <p> With this option you request your SIP provider to hide your identity from the called party. This will only hide your identity, e.g. your SIP address, telephone number. It does <b>not</b> hide your IP address. </p> <p> <b>Warning:</b> not all providers support identity hiding. </p> <p> Med denna inställning kan du be din SIP-leverantör dölja din identitet för den du ringer upp. Detta döljer bara din identitet. D.v.s. din SIP-adress eller telefonnummer. Det döljer <b>inte</b> din IP-adress. </p> <p> <b>Varning:</b> Inte alla SIP-leverantörer stöder dold identitet. </p> &OK &OK &Cancel &Avbryt Not all SIP providers support identity hiding. Make sure your SIP provider supports it if you really need it. Inte alla SIP-leverantörer stöder dold identitet. Se till att din SIP-leverantör stöder det om du verkligen behöver det. F10 F10 LogViewForm Twinkle - Log Twinkle - Logg Contents of the current log file (~/.twinkle/twinkle.log) Innehåll i nuvarande loggfil (~/.twinkle/twinkle.log) &Close &Stäng Alt+C Alt+S C&lear &Töm Alt+L Alt+T Clear the log window. This does <b>not</b> clear the log file itself. Töm loggfönstret. Detta tömmer <b>inte</b> själva loggfilen. MessageForm Twinkle - Instant message Twinkle - Snabbmeddelande &To: &Till: The user that will send the message. Användaren som ska skicka meddelandet. The address of the user that you want to send a message. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Address book Adressbok Select an address from the address book. Välj en adress från adressboken. &User profile: &Användarprofil: Conversation Konversation Type your message here and then press "send" to send it. Skriv ditt meddelande här och tryck sedan på "Skicka" för att skicka det. &Send &Skicka Alt+S Alt+S Send the message. Skicka meddelandet. Instant message toolbar Verktygsrad för snabbmeddelanden Send file... Skicka fil... Send file Skicka fil image size is scaled down in preview bildstorleken är nedskalad i förhandsvisning Delivery failure Leverans misslyckades Delivery notification Leveransnotifiering Open with %1... Öppna med %1... Open with... Öppna med... Save attachment as... Spara bilaga som... File already exists. Do you want to overwrite this file? Filen finns redan. Vill du skriva över denna fil? Failed to save attachment. Misslyckades med att spara bilaga. %1 is typing a message. %1 skriver ett meddelande. F10 F10 Size MessageFormView sending message skickar meddelande MphoneForm Twinkle Twinkle &Call: Label in front of combobox to enter address &Ring: The address that you want to call. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Adressen som du vill ringa. Detta kan vara en komplett SIP-adress som <b>sip:exempel@exempel.com</b> eller bara användarnamnet eller telefonnumret i adressen. När du inte anger en komplett adress kommer Twinkle fylla ut adressen med domännamnet från din användarprofil. Address book Adressbok Select an address from the address book. Välj en adress från adressboken. Dial Ring Dial the address. Ring till adressen. &User: &Användare: The user that will make the call. Användaren som ska ringa samtalet. Auto answer indication. Call redirect indication. Do not disturb indication. Missed call indication. Registration status. Display Line status Linjestatus Line &1: Linje &1: Alt+1 Alt+1 Click to switch to line 1. Klicka för att växla till linje 1. From: Från: To: Till: Subject: Ämne: idle No need to translate Overksam Conference call Konferenssamtal Transferring call sas No need to translate sas Short authentication string g711a/g711a No need to translate g711a/g711a Audio codec Ljudkodek 0:00:00 0:00:00 Call duration Samtalslängd sip:from No need to translate sip:från sip:to No need to translate sip:till subject No need to translate ämne photo No need to translate foto Line &2: Linje &2: Alt+2 Alt+2 Click to switch to line 2. Klicka för att växla till linje 2. &File &Arkiv &Edit &Redigera C&all &Ring Activate line Aktivera linje &Registration &Registrering &Services &Tjänster &View &Visa &Help &Hjälp Quit Avsluta &Quit &Avsluta Ctrl+Q Ctrl+A About Twinkle Om Twinkle &About Twinkle &Om Twinkle Call toolbar text Ring &Call... call menu text &Ring... Call someone Ring någon F5 F5 Answer toolbar text Svara &Answer menu text &Svara Answer incoming call Svara inkommande samtal F6 F6 Bye toolbar text Adjö &Bye menu text &Lägg på Release call Esc Esc Reject toolbar text Avvisa &Reject menu text &Avvisa Reject incoming call Avvisa inkommande samtal F8 F8 &Hold menu text &Parkera Put a call on hold, or retrieve a held call Pakera ett samtal eller återuppta ett parkerat samtal Redirect toolbar text Koppla vidare R&edirect... menu text Koppla vidar&e... Redirect incoming call without answering Koppla vidare inkommande samtal utan att svara Dtmf toolbar text Dtmf &Dtmf... menu text &Dtmf... Open keypad to enter digits for voice menu's Öppna knappsats för att mata in siffror för röstmenyer Register Registrera &Register &Registrera Deregister Avregistrera &Deregister Avre&gistrera Deregister this device Avregistrera denna enhet Show registrations Visa registreringar &Show registrations &Visa registreringar Terminal capabilities Terminalförmågor &Terminal capabilities... menu text &Terminalförmågor... Request terminal capabilities from someone Begär terminalförmågor från någon Do not disturb Stör inte &Do not disturb Stör &inte Call redirection Vidarekoppla samtal Call &redirection... Vidarekoppla sa&mtal... Redial toolbar text Ring igen &Redial menu text Ring &igen Repeat last call Upprepa senaste samtalet F12 F12 About Qt Om Qt About &Qt Om &Qt User profile Användarprofil &User profile... &Användarprofil... Conf toolbar text Konf &Conference menu text &Konferens Join two calls in a 3-way conference Mute toolbar text Tyst &Mute menu text &Tyst Mute a call Stäng av mikrofonen Xfer toolbar text Koppla Trans&fer... menu text Kopp&la... Transfer call Koppla samtal System settings Systeminställningar &System settings... &Systeminställningar... Deregister all Avregistrera alla Deregister &all Avregistrera &alla Deregister all your registered devices Avregistera alla dina registrerade enheter Auto answer Svara automatiskt &Auto answer S&vara automatiskt Log Logg &Log... &Logg... Call history Samtalshistorik Call &history... Samtals&historik... F9 F9 Change user ... Byt användare... &Change user ... &Byt användare... Activate or de-activate users Aktivera eller inaktivera användare What's This? Vad är detta? What's &This? Vad är &detta? Shift+F1 Shift+F1 Line 1 Linje 1 Line 2 Linje 2 idle overksam dialing ringer attempting call, please wait försöker ringa samtal, vänta incoming call inkommande samtal establishing call, please wait etablerar samtal, vänta established etablerat established (waiting for media) etablerat (väntar på media) releasing call, please wait unknown state okänt tillstånd Voice is encrypted Röstkanal är krypterad Click to confirm SAS. Klicka för att bekräfta SAS. Click to clear SAS verification. Transfer consultation User: Användare: Call: Ring: Hide identity Dölj identitet Registration status: Registreringsstatus: Registered Registrerad Failed Misslyckades Not registered Inte registrerad Click to show registrations. Klicka för att visa registreringar. No users are registered. Inga användare är registrerade. %1 new, 1 old message %1 nytt, ett gammalt meddelande %1 new, %2 old messages %1 nya, %2 gamla meddelanden 1 new message Ett nytt meddelande %1 new messages %1 nya meddelanden 1 old message Ett gammalt meddelande %1 old messages %1 gamla meddelanden Messages waiting Meddelanden väntar No messages Inga meddelanden <b>Voice mail status:</b> <b>Status för röstbrevlåda:</b> Failure Misslyckades Unknown Okänt Click to access voice mail. Klicka för att komma åt röstmeddelanden. Do not disturb active for: Stör inte är aktivt för: Redirection active for: Vidarekoppling är aktivt för: Auto answer active for: Svara automatiskt är aktivt för: Click to activate/deactivate Klicka för att aktivera/inaktivera Click to activate Klicka för att aktivera Do not disturb is not active. Stör inte är inte aktiverat. Redirection is not active. Vidarekoppling är inte aktiverat. Auto answer is not active. Svara automatiskt är inte aktiverat. Click to see call history for details. Klicka för att se samtalshistorik för detaljer. You have no missed calls. Du har inga missade samtal. You missed 1 call. Du missade ett samtal. You missed %1 calls. Du missade %1 samtal. Starting user profiles... Startar användarprofiler... The following profiles are both for user %1 Följande profiler är båda för användaren %1 You can only run multiple profiles for different users. Du kan endast köra flera profiler för olika användare. You have changed the SIP UDP port. This setting will only become active when you restart Twinkle. not provisioned You must provision your voice mail address in your user profile, before you can access it. The line is busy. Cannot access voice mail. Linjen är upptagen. Kan inte komma åt röstmeddelanden. The voice mail address %1 is an invalid address. Please provision a valid address in your user profile. Buddy list Kompislista You can create a separate buddy list for each user profile. You can only see availability of your buddies and publish your own availability if your provider offers a presence server. Message waiting indication. &Message &Meddelande &Display Voice mail Röstbrevlåda &Voice mail &Röstbrevlåda Access voice mail F11 F11 Msg Chatt Instant &message... Snabb&meddelande... Instant message Snabbmeddelande &Buddy list &Kompislista &Call... &Ring... &Edit... &Redigera... &Delete &Ta bort O&ffline Fr&ånkopplad &Online A&nsluten &Change availability &Ändra tillgänglighet &Add buddy... &Lägg till kompis... Failed to save buddy list: %1 Misslyckades med att spara kompislista. %1 F10 F10 Diamondcard Manual &Manual Sign up &Sign up... Recharge... Balance history... Call history... Admin center... Recharge Balance history Admin center Call Ring &Answer &Svara Answer Svara &Bye &Lägg på Bye Adjö &Reject &Avvisa Reject Avvisa &Hold &Parkera Hold R&edirect... Koppla vidar&e... Redirect Koppla vidare &Dtmf... &Dtmf... Dtmf Dtmf &Terminal capabilities... &Terminalförmågor... &Redial Ring &igen Redial Ring igen &Conference &Konferens Conf Konf &Mute &Tyst Mute Tyst Trans&fer... Kopp&la... Xfer Koppla NumberConversionForm Twinkle - Number conversion Twinkle - Nummerkonvertering &Match expression: &Matcha uttryck: &Replace: &Ersätt: Perl style format string for the replacement number. Perl style regular expression matching the number format you want to modify. &OK &OK Alt+O Alt+O &Cancel &Avbryt Alt+C Alt+A Match expression may not be empty. Uttryck att matcha mot kan inte vara tomt. Replace value may not be empty. Ersättningsvärde kan inte vara tomt. Invalid regular expression. Ogiltigt reguljärt uttryck. RedirectForm Twinkle - Redirect Twinkle - Koppla vidare Redirect incoming call to Koppla vidare inkommande samtal till You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. Du kan ange upp till 3 destinationer att koppla vidare samtalet till. Om första destinationen inte svarar går man vidare till andra destinationen och så vidare. &3rd choice destination: Destination i &tredje hand: &2nd choice destination: Destination i &andra hand: &1st choice destination: Destination i &första hand: Address book Adressbok Select an address from the address book. Välj en adress från adressboken. &OK &OK &Cancel &Avbryt F10 F10 F12 F12 F11 F11 SelectNicForm Twinkle - Select NIC Twinkle - Välj nätverkskort Select the network interface/IP address that you want to use: Välj nätverkskort/IP-adress som du vill använda: You have multiple IP addresses. Here you must select which IP address should be used. This IP address will be used inside the SIP messages. Du har flera IP-adresser. Här måste du välja vilken IP-adress som ska användas. Denna IP-adress kommer användas i alla SIP-meddelanden. Set as default &IP Ange som standard-&IP Alt+I Alt+I Make the selected IP address the default IP address. The next time you start Twinkle, this IP address will be automatically selected. Gör den valda IP-adressen till standard-IP-adress. Nästa gång som du startar Twinkle kommer denna IP-adress att väljas automatiskt. Set as default &NIC Ange som standard&nätverkskort Alt+N Alt+N Make the selected network interface the default interface. The next time you start Twinkle, this interface will be automatically selected. Gör det valda nätverkskortet till standardgränssnitt. Nästa gång som du startar Twinkle kommer detta nätverkskort att väljas automatiskt. &OK &OK Alt+O Alt+O If you want to remove or change the default at a later time, you can do that via the system settings. Om du vill ta bort eller ändra standardvärdet vid ett senare tillfälle så kan du göra det via systeminställningarna. SelectProfileForm Twinkle - Select user profile Twinkle - Välj användarprofil Select user profile(s) to run: Välj användarprofil(er) att köra: User profile Användarprofil Tick the check boxes of the user profiles that you want to run and press run. Kryssa för de användarprofiler som du vill köra och tryck på Kör. &New &Ny Alt+N Alt+N Create a new profile with the profile editor. Skapa en ny profil med profilredigeraren. &Wizard &Guide Alt+W Alt+G Create a new profile with the wizard. Skapa en ny profil med guiden. &Edit &Redigera Alt+E Alt+R Edit the highlighted profile. Redigera markerad profil. &Delete &Ta bort Alt+D Alt+T Delete the highlighted profile. Ta bort markerad profil. Ren&ame Byt &namn Alt+A Alt+N Rename the highlighted profile. Byt namn på markerad profil. &Set as default &Ange som standard Alt+S Alt+A Make the selected profiles the default profiles. The next time you start Twinkle, these profiles will be automatically run. Gör de markerade profilerna till standardprofiler. Nästa gång som du startar Twinkle så kommer dessa profiler att köras automatiskt. &Run &Kör Alt+R Alt+K Run Twinkle with the selected profiles. Kör Twinkle med markerade profiler. S&ystem settings S&ysteminställningar Alt+Y Alt+Y Edit the system settings. Redigera systeminställningarna. &Cancel &Avbryt Alt+C Alt+A <html>Before you can use Twinkle, you must create a user profile.<br>Click OK to create a profile.</html> <html>Innan du kan använda Twinkle måste du skapa en användarprofil.<br>Klicka OK för att skapa en profil.</html> &Profile editor &Profilredigerare <html>Next you may adjust the system settings. You can change these settings always at a later time.<br><br>Click OK to view and adjust the system settings.</html> You did not select any user profile to run. Please select a profile. Du valde inte någon användarprofil att köra. Välj en profil. Are you sure you want to delete profile '%1'? Är du säker på att du vill ta bort profilen "%1"? Delete profile Ta bort profil Failed to delete profile. Misslyckades med att ta bort profil. Failed to rename profile. Misslyckades med att byta namn på profil. <p>If you want to remove or change the default at a later time, you can do that via the system settings.</p> Cannot find .twinkle directory in your home directory. Kan inte hitta katalogen .twinkle i din hemkatalog. Create profile Ed&itor Alt+I Alt+I Dia&mondcard Alt+M Alt+M Modify profile Startup profile &Diamondcard Create a profile for a Diamondcard account. With a Diamondcard account you can make worldwide calls to regular and cell phones and send SMS messages. <html>You can use the profile editor to create a profile. With the profile editor you can change many settings to tune the SIP protocol, RTP and many other things.<br><br>Alternatively you can use the wizard to quickly setup a user profile. The wizard asks you only a few essential settings. If you create a user profile with the wizard you can still edit the full profile with the profile editor at a later time.<br><br> You can create a Diamondcard account to make worldwide calls to regular and cell phones and send SMS messages.<br><br> Choose what method you wish to use.</html> SelectUserForm Twinkle - Select user Twinkle - Välj användare &Cancel &Avbryt Alt+C Alt+A &Select all &Välj alla Alt+S Alt+V &OK &OK Alt+O Alt+O C&lear all &Töm alla Alt+L Alt+T purpose No need to translate syfte User Användare Register Registrera Select users that you want to register. Välj användare som du vill registrera. Deregister Avregistrera Select users that you want to deregister. Välj användare som du vill avregistrera. Deregister all devices Avregistrera alla enheter Select users for which you want to deregister all devices. Välj användare för vilka du vill avregistrera alla enheter. Do not disturb Stör inte Select users for which you want to enable 'do not disturb'. Välj användare för vilka du vill aktivera 'stör ej'. Auto answer Svara automatiskt Select users for which you want to enable 'auto answer'. Välj användare för vilka du vill aktivera "svara automatiskt". SendFileForm Twinkle - Send File Twinkle - Skicka fil Select file to send. Välj fil att skicka. &File: &Fil: &Subject: &Ämne: &OK &OK Alt+O Alt+O &Cancel &Avbryt Alt+C Alt+A File does not exist. Filen finns inte. Send file... Skicka fil... SrvRedirectForm Twinkle - Call Redirection User: Användare: There are 3 redirect services:<p> <b>Unconditional:</b> redirect all calls </p> <p> <b>Busy:</b> redirect a call if both lines are busy </p> <p> <b>No answer:</b> redirect a call when the no-answer timer expires </p> &Unconditional &Redirect all calls Alt+R Activate the unconditional redirection service. Redirect to You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. Du kan ange upp till 3 destinationer att koppla vidare samtalet till. Om första destinationen inte svarar går man vidare till andra destinationen och så vidare. &3rd choice destination: Destination i &3:e hand: &2nd choice destination: Destination i &2:a hand: &1st choice destination: Destination i &1:a hand: Address book Adressbok Select an address from the address book. Välj en adress från adressboken. &Busy &Upptagen &Redirect calls when I am busy Activate the redirection when busy service. &No answer &Inget svar &Redirect calls when I do not answer Activate the redirection on no answer service. &OK &OK Alt+O Alt+O Accept and save all changes. Acceptera och spara alla ändringar. &Cancel &Avbryt Alt+C Alt+A Undo your changes and close the window. Ångra dina ändringar och stäng fönstret. You have entered an invalid destination. Du har angivit en ogiltig destination. F10 F10 F11 F11 F12 F12 SysSettingsForm Twinkle - System Settings Twinkle - Systeminställningar General Generellt Audio Ljud Ring tones Ringsignaler Address book Adressbok Network Nätverk Log Logg Select a category for which you want to see or modify the settings. Välj den kategori som du vill titta eller ändra inställningar för. &OK &OK Alt+O Alt+O Accept and save your changes. Acceptera och spara dina ändringar. &Cancel &Avbryt Alt+C Alt+A Undo all your changes and close the window. Ångra alla ändringar och stäng fönstret. Sound Card Ljudkort Select the sound card for playing the ring tone for incoming calls. Välj det ljudkort som ska användas för att spela upp ringsignalen när någon ringer. Select the sound card to which your microphone is connected. Välj det ljudkort som din mikrofon är inkopplad till. Select the sound card for the speaker function during a call. Välj det ljudkort som högtalaren är inkopplad till under samtal. &Speaker: &Högtalare: &Ring tone: &Ringsignal: Other device: Annan enhet: &Microphone: &Mikrofon: &Validate devices before usage &Validera enheterna innan användning Alt+V Alt+V <p> Twinkle validates the audio devices before usage to avoid an established call without an audio channel. <p> On startup of Twinkle a warning is given if an audio device is inaccessible. <p> If before making a call, the microphone or speaker appears to be invalid, a warning is given and no call can be made. <p> If before answering a call, the microphone or speaker appears to be invalid, a warning is given and the call will not be answered. Reduce &noise from the microphone Reducera &brus från mikrofonen Alt+N Alt+B Advanced Avancerat OSS &fragment size: 16 16 32 32 64 64 128 128 256 256 The ALSA play period size influences the real time behaviour of your soundcard for playing sound. If your sound frequently drops while using ALSA, you might try a different value here. ALSA &play period size: &ALSA capture period size: The OSS fragment size influences the real time behaviour of your soundcard. If your sound frequently drops while using OSS, you might try a different value here. The ALSA capture period size influences the real time behaviour of your soundcard for capturing sound. If the other side of your call complains about frequently dropping sound, you might try a different value here. &Max log size: &Maximal loggstorlek: The maximum size of a log file in MB. When the log file exceeds this size, a backup of the log file is created and the current log file is zapped. Only one backup log file will be kept. MB MB Log &debug reports Alt+D Alt+T Indicates if reports marked as "debug" will be logged. Log &SIP reports Logga &SIP-rapporter Alt+S Alt+S Indicates if SIP messages will be logged. Log S&TUN reports Logga S&TUN-rapporter Alt+T Alt+T Indicates if STUN messages will be logged. Log m&emory reports Alt+E Alt+R Indicates if reports concerning memory management will be logged. System tray Aktivitetsfält Create &system tray icon on startup Skapa ikon i a&ktivitetsfält vid uppstart Enable this option if you want a system tray icon for Twinkle. The system tray icon is created when you start Twinkle. &Hide in system tray when closing main window Alt+H Alt+D Enable this option if you want Twinkle to hide in the system tray when you close the main window. Startup Uppstart S&tartup hidden in system tray Next time you start Twinkle it will immediately hide in the system tray. This works best when you also select a default user profile. Default user profiles Användarprofiler som standard If you always use the same profile(s), then you can mark these profiles as default here. The next time you start Twinkle, you will not be asked to select which profiles to run. The default profiles will automatically run. Services Tjänster Call &waiting Samtal &väntar Alt+W Alt+V With call waiting an incoming call is accepted when only one line is busy. When you disable call waiting an incoming call will be rejected when one line is busy. Hang up &both lines when ending a 3-way conference call. Alt+B Alt+B Hang up both lines when you press bye to end a 3-way conference call. When this option is disabled, only the active line will be hung up and you can continue talking with the party on the other line. &Maximum calls in call history: The maximum number of calls that will be kept in the call history. &Auto show main window on incoming call after Alt+A Alt+B When the main window is hidden, it will be automatically shown on an incoming call after the number of specified seconds. Number of seconds after which the main window should be shown. secs s &RTP port: &RTP-port: The UDP port used for sending and receiving RTP for the first line. The UDP port for the second line is 2 higher. E.g. if port 8000 is used for the first line, then the second line uses port 8002. When you use call transfer then the next even port (eg. 8004) is also used. Ring tone Ringsignal &Play ring tone on incoming call S&pela upp ringsignal vid inkommande samtal Alt+P Alt+P Indicates if a ring tone should be played when a call comes in. Indikerar om en ringsignal ska spelas upp när ett samtal kommer in. &Default ring tone Stan&dardringsignal Play the default ring tone when a call comes in. Spela upp standardringsignalen när ett samtal kommer in. C&ustom ring tone A&npassad ringsignal Alt+U Alt+N Play a custom ring tone when a call comes in. Spela upp en anpassad ringsignal när ett samtal kommer in. Specify the file name of a .wav file that you want to be played as ring tone. Ring back tone P&lay ring back tone when network does not play ring back tone Alt+L Alt+T <p> Play ring back tone while you are waiting for the far-end to answer your call. </p> <p> Depending on your SIP provider the network might provide ring back tone or an announcement. </p> D&efault ring back tone Play the default ring back tone. Cu&stom ring back tone Play a custom ring back tone. Specify the file name of a .wav file that you want to be played as ring back tone. &Lookup name for incoming call On an incoming call, Twinkle will try to find the name belonging to the incoming SIP address in your address book. This name will be displayed. Ove&rride received display name Alt+R The caller may have provided a display name already. Tick this box if you want to override that name with the name you have in your address book. Lookup &photo for incoming call Slå upp &foto för inkommande samtal Lookup the photo of a caller in your address book and display it on an incoming call. none This is the 'none' in default IP address combo ingen none This is the 'none' in default network interface combo ingen Ring tones Description of .wav files in file dialog Ringsignaler Choose ring tone Välj ringsignal Ring back tones Description of .wav files in file dialog Choose ring back tone Maximum allowed size (0-65535) in bytes of an incoming SIP message over UDP. &SIP port: &SIP-port: Max. SIP message size (&TCP): The UDP/TCP port used for sending and receiving SIP messages. Max. SIP message size (&UDP): Maximum allowed size (0-4294967295) in bytes of an incoming SIP message over TCP. Select ring tone file. Select ring back tone file. W&eb browser command: Command to start your web browser. If you leave this field empty Twinkle will try to figure out your default web browser. 512 512 1024 1024 Tip: for crackling sound with PulseAudio, set play period size to maximum. Enable in-call OSD SysTrayPopup Answer Svara Reject Avvisa Incoming Call TermCapForm Twinkle - Terminal Capabilities Twinkle - Terminalförmågor &From: &Från: Get terminal capabilities of Hämta terminalförmågor för &To: &Till: The address that you want to query for capabilities (OPTION request). This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Adressen som du vill fråga efter förmågor (OPTION-begäran). Detta kan vara en fullständig SIP-adress som <b>sip:exempel@exempel.se</b> eller bara användardelen eller telefonnumret av den fullständiga adressen. När du inte anger en fullständig adress så kommer Twinkle att komplettera adressen genom att använda domänvärdet för din användarprofil. Address book Adressbok Select an address from the address book. Välj en adress från adressboken. &OK &OK &Cancel &Avbryt F10 F10 TransferForm Twinkle - Transfer Twinkle - Koppla Transfer call to Koppla samtal till &To: &Till: The address of the person you want to transfer the call to. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Address book Adressbok Select an address from the address book. Välj en adress från adressboken. Type of transfer Typ av koppling &Blind transfer &Blind koppling Alt+B Alt+B Transfer the call to a third party without contacting that third party yourself. T&ransfer with consultation Alt+R Before transferring the call to a third party, first consult the party yourself. Transfer to other &line Alt+L Alt+T Connect the remote party on the active line with the remote party on the other line. &OK &OK Alt+O Alt+O &Cancel &Avbryt F10 F10 TwinkleCore Cannot open file for reading: %1 Kan inte öppna fil för läsning: %1 File system error while reading file %1 . Fel i filsystemet vid läsning från filen %1. Cannot open file for writing: %1 Kan inte öppna fil för skrivning: %1 File system error while writing file %1 . Fel i filsystemet vid skrivning till filen %1. Anonymous Anonym Warning: Varning: Failed to create log file %1 . Kunde inte skapa loggfil %1. Excessive number of socket errors. För stort antal uttagsfel (socket). Built with support for: Byggd med stöd för: Contributions: Bidrag: This software contains the following software from 3rd parties: Den här programmet innehåller följande tredjepartsprogramvara: * GSM codec from Jutta Degener and Carsten Bormann, University of Berlin * GSM codec från Jutta Degener och Carsten Bormann vid Berlins universitet * G.711/G.726 codecs from Sun Microsystems (public domain) * G.711/G.726 kodekar från Sun Microsystems (public domain) * iLBC implementation from RFC 3951 (www.ilbcfreeware.org) * iLBC implementation från RFC 3951 (www.ilbcfreeware.org) * Parts of the STUN project at http://sourceforge.net/projects/stun * Delar av STUN-projektet från http://sourceforge.net/projects/stun * Parts of libsrv at http://libsrv.sourceforge.net/ * Delar av libsrv från http://libsrv.sourceforge.net For RTP the following dynamic libraries are linked: För RTP länkas följande dynamiska bibliotek in: Translated to english by <your name> Översatt till svenska av Daniel Nylander Directory %1 does not exist. Katalogen %1 finns inte. Cannot open file %1 . Kan inte öppna filen %1. %1 is not set to your home directory. %1 är inte satt till din hemkatalog. Directory %1 (%2) does not exist. Katalogen %1 (%2) finns inte. Cannot create directory %1 . Kan inte skapa katalogen %1. Lock file %1 already exist, but cannot be opened. Låsfil %1 finns redan, men kan inte öppnas. %1 is already running. Lock file %2 already exists. %1 körs redan. Låsfilen %2 finns redan. Cannot create %1 . Kan inte skapa %1. Cannot write to %1 . Kan inte skriva till %1. Syntax error in file %1 . Syntaxfel i filen %1. Failed to backup %1 to %2 Kunde inte kopiera %1 till %2 unknown name (device is busy) okänt namn (enheten är upptagen) Default device Standardenhet Cannot access the ring tone device (%1). Cannot access the speaker (%1). Kan inte komma åt högtalaren (%1). Cannot access the microphone (%1). Kan inte komma åt mikrofonen (%1). Call transfer - %1 Sound card cannot be set to full duplex. Ljudkortet kan inte ställas in i full duplex-läge. Cannot set buffer size on sound card. Kan inte ställa in buffertstorlek på ljudkortet. Sound card cannot be set to %1 channels. Ljudkortet kan inte ställas in till %1 kanaler. Cannot set sound card to 16 bits recording. Cannot set sound card to 16 bits playing. Cannot set sound card sample rate to %1 Opening ALSA driver failed Cannot open ALSA driver for PCM playback Cannot resolve STUN server: %1 Kan inte slå upp STUN-server: %1 You are behind a symmetric NAT. STUN will not work. Configure a public IP address in the user profile and create the following static bindings (UDP) in your NAT. Du är bakom en symmetrisk NAT. STUN kommer inte fungera. Konfigurera en publik IP-adress i användarprofilen och skapa följande statiska bindningar (UDP) i din NAT. public IP: %1 --> private IP: %2 (SIP signaling) publik IP: %1 --> privat IP: %2 (SIP-signalering) public IP: %1-%2 --> private IP: %3-%4 (RTP/RTCP) publik IP: %1-%2 --> privat IP: %3-%4 (RTP/RTCP) Cannot reach the STUN server: %1 Kan inte kontakta STUN-servern: %1 If you are behind a firewall then you need to open the following UDP ports. Om du är bakom en brandvägg behöver du öppna följande UDP-portar. Port %1 (SIP signaling) Port %1 (SIP-signalering) Ports %1-%2 (RTP/RTCP) Portar %1-%2 (RTP/RTCP) NAT type discovery via STUN failed. STUN kunde inte detektera NAT-typ. Failed to create file %1 Misslyckades med att skapa filen %1 Failed to write data to file %1 Misslyckades med att skriva data till filen %1 Cannot receive incoming TCP connections. Kan inte ta emot inkommande TCP-anslutningar. Cannot open ALSA driver for PCM capture Kan inte öppna ALSA-drivrutin för PCM-fångst Failed to send message. Misslyckades med att skicka meddelande. Cannot lock %1 . UserProfileForm Twinkle - User Profile Twinkle - Användarprofil User profile: Användarprofil: Select which profile you want to edit. Välj den profil du vill ändra. User Användare SIP server SIP-server Voice mail Röstbrevlåda RTP audio RTP-ljud SIP protocol SIP-protokoll NAT NAT Address format Adressformat Timers Ring tones Ringsignaler Scripts Skript Security Säkerhet Select a category for which you want to see or modify the settings. Välj en kategori för vilken du vill se eller ändra inställningar. &OK &OK Alt+O Alt+O Accept and save your changes. Acceptera och spara dina ändringar. &Cancel &Avbryt Alt+C Alt+A Undo all your changes and close the window. Ångra alla dina ändringar och stäng fönstret. SIP account SIP-konto &User name*: &Användarnamn*: &Domain*: &Domän*: Or&ganization: Or&ganisation: The SIP user name given to you by your provider. It is the user part in your SIP address, <b>username</b>@domain.com This could be a telephone number. <br><br> This field is mandatory. SIP-användarnamnet som din leverantör levererat till dig. Detta är användardelen av din SIP-adress, <b>användarnamn</b>@domän.se. Detta kan vara ett telefonnummer. <br><br> Detta fält är obligatoriskt. The domain part of your SIP address, username@<b>domain.com</b>. Instead of a real domain this could also be the hostname or IP address of your <b>SIP proxy</b>. If you want direct IP phone to IP phone communications then you fill in the hostname or IP address of your computer. <br><br> This field is mandatory. Domändelen av din SIP-adress, användarnamn@<b>domän.se</b>. Istället för en riktig domän så kan detta även vara värdnamnet eller IP-adressen för din <b>SIP-proxy</b>. Om du vill använda direktkommunikation mellan IP-telefon och IP-telefon så kan du fylla i värdnamnet eller IP-adress för din dator. <br><br> Detta fält är obligatoriskt. You may fill in the name of your organization. When you make a call, this might be shown to the called party. Du kan fylla i namnet för din organisation. När du ringer ett samtal så kan dock detta visas för motparten. This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. Detta är helt enkelt ditt fullständiga namn, t.ex. Sven Svensson. Det används endast för visning. När du ringer ett samtal kan dock detta namn visas för motparten. &Your name: &Ditt namn: SIP authentication SIP-autentisering &Realm: Authentication &name: &Password: &Lösenord: The realm for authentication. This value must be provided by your SIP provider. If you leave this field empty, then Twinkle will try the user name and password for any realm that it will be challenged with. Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Ditt SIP-autentiseringsnamn. Ganska ofta är detta samma som ditt SIP-användarnamn. Det kan dock vara ett annat namn. Your password for authentication. Ditt lösenord för autentisering. Registrar &Registrar: The hostname, domain name or IP address of your registrar. If you use an outbound proxy that is the same as your registrar, then you may leave this field empty and only fill in the address of the outbound proxy. &Expiry: The registration expiry time that Twinkle will request. seconds sekunder Re&gister at startup Re&gistrera vid uppstart Alt+G Alt+G Indicates if Twinkle should automatically register when you run this user profile. You should disable this when you want to do direct IP phone to IP phone communication without a SIP proxy. Outbound Proxy Utgående proxy &Use outbound proxy &Använd utgående proxy Alt+U Alt+A Indicates if Twinkle should use an outbound proxy. If an outbound proxy is used then all SIP requests are sent to this proxy. Without an outbound proxy, Twinkle will try to resolve the SIP address that you type for a call invitation for example to an IP address and send the SIP request there. Outbound &proxy: Utgående &proxy: &Send in-dialog requests to proxy Alt+S Alt+V SIP requests within a SIP dialog are normally sent to the address in the contact-headers exchanged during call setup. If you tick this box, that address is ignored and in-dialog request are also sent to the outbound proxy. &Don't send a request to proxy if its destination can be resolved locally. Alt+D Alt+T When you tick this option Twinkle will first try to resolve a SIP address to an IP address itself. If it can, then the SIP request will be sent there. Only when it cannot resolve the address, it will send the SIP request to the proxy (note that an in-dialog request will only be sent to the proxy in this case when you also ticked the previous option.) The hostname, domain name or IP address of your outbound proxy. Värdnamnet, domännamnet eller IP-adressen för din utgående proxy. Co&decs Ko&dekar Codecs Kodekar Available codecs: Tillgängliga kodekar: G.711 A-law G.711 A-law G.711 u-law G.711 u-law GSM GSM speex-nb (8 kHz) speex-nb (8 kHz) speex-wb (16 kHz) speex-wb (16 kHz) speex-uwb (32 kHz) speex-uwb (32 kHz) List of available codecs. Lista över tillgängliga kodekar. Move a codec from the list of available codecs to the list of active codecs. Flytta en kodek från listan för tillgängliga kodekar till listan för aktiva kodekar. Move a codec from the list of active codecs to the list of available codecs. Flytta en kodek från listan för aktiva kodekar till listan för tillgängliga kodekar. Active codecs: Aktiva kodekar: List of active codecs. These are the codecs that will be used for media negotiation during call setup. The order of the codecs is the order of preference of use. Move a codec upwards in the list of active codecs, i.e. increase its preference of use. Move a codec downwards in the list of active codecs, i.e. decrease its preference of use. &G.711/G.726 payload size: The preferred payload size for the G.711 and G.726 codecs. ms ms &Follow codec preference from far end on incoming calls Alt+F <p> For incoming calls, follow the preference from the far-end (SDP offer). Pick the first codec from the SDP offer that is also in the list of active codecs. <p> If you disable this option, then the first codec from the active codecs that is also in the SDP offer is picked. Follow codec &preference from far end on outgoing calls Alt+P Alt+P <p> For outgoing calls, follow the preference from the far-end (SDP answer). Pick the first codec from the SDP answer that is also in the list of active codecs. <p> If you disable this option, then the first codec from the active codecs that is also in the SDP answer is picked. &iLBC &iLBC iLBC iLBC i&LBC payload type: iLBC &payload size (ms): The dynamic type value (96 or higher) to be used for iLBC. 20 20 30 30 The preferred payload size for iLBC. &Speex &Speex Speex Speex Perceptual &enhancement Alt+E Alt+R Perceptual enhancement is a part of the decoder which, when turned on, tries to reduce (the perception of) the noise produced by the coding/decoding process. In most cases, perceptual enhancement make the sound further from the original objectively (if you use SNR), but in the end it still sounds better (subjective improvement). &Ultra wide band payload type: &VAD &VAD Alt+V Alt+V &Wide band payload type: V&BR V&BR Alt+B Alt+B Variable bit-rate (VBR) allows a codec to change its bit-rate dynamically to adapt to the "difficulty" of the audio being encoded. In the example of Speex, sounds like vowels and high-energy transients require a higher bit-rate to achieve good quality, while fricatives (e.g. s,f sounds) can be coded adequately with less bits. For this reason, VBR can achieve a lower bit-rate for the same quality, or a better quality for a certain bit-rate. Despite its advantages, VBR has two main drawbacks: first, by only specifying quality, there's no guarantee about the final average bit-rate. Second, for some real-time applications like voice over IP (VoIP), what counts is the maximum bit-rate, which must be low enough for the communication channel. The dynamic type value (96 or higher) to be used for speex wide band. Co&mplexity: DT&X DT&X Alt+X Alt+X Discontinuous transmission is an addition to VAD/VBR operation, that allows one to stop transmitting completely when the background noise is stationary. The dynamic type value (96 or higher) to be used for speex narrow band. With Speex, it is possible to vary the complexity allowed for the encoder. This is done by controlling how the search is performed with an integer ranging from 1 to 10 in a way that's similar to the -1 to -9 options to gzip and bzip2 compression utilities. For normal use, the noise level at complexity 1 is between 1 and 2 dB higher than at complexity 10, but the CPU requirements for complexity 10 is about 5 times higher than for complexity 1. In practice, the best trade-off is between complexity 2 and 4, though higher settings are often useful when encoding non-speech sounds like DTMF tones. &Narrow band payload type: G.726 G.726 G.726 &40 kbps payload type: The dynamic type value (96 or higher) to be used for G.726 40 kbps. The dynamic type value (96 or higher) to be used for G.726 32 kbps. G.726 &24 kbps payload type: The dynamic type value (96 or higher) to be used for G.726 24 kbps. G.726 &32 kbps payload type: The dynamic type value (96 or higher) to be used for G.726 16 kbps. G.726 &16 kbps payload type: Codeword &packing order: RFC 3551 RFC 3551 ATM AAL2 ATM AAL2 There are 2 standards to pack the G.726 codewords into an RTP packet. RFC 3551 is the default packing method. Some SIP devices use ATM AAL2 however. If you experience bad quality using G.726 with RFC 3551 packing, then try ATM AAL2 packing. DT&MF DT&MF DTMF DTMF The dynamic type value (96 or higher) to be used for DTMF events (RFC 2833). DTMF vo&lume: The power level of the DTMF tone in dB. The pause after a DTMF tone. DTMF &duration: DTMF payload &type: DTMF &pause: dB dB Duration of a DTMF tone. DTMF t&ransport: DTMF-t&ransport: Auto Auto RFC 2833 RFC 2833 Inband Out-of-band (SIP INFO) <h2>RFC 2833</h2> <p>Send DTMF tones as RFC 2833 telephone events.</p> <h2>Inband</h2> <p>Send DTMF inband.</p> <h2>Auto</h2> <p>If the far end of your call supports RFC 2833, then a DTMF tone will be send as RFC 2833 telephone event, otherwise it will be sent inband. </p> <h2>Out-of-band (SIP INFO)</h2> <p> Send DTMF out-of-band via a SIP INFO request. </p> General Allmänt Protocol options Protokollalternativ Call &Hold variant: RFC 2543 RFC 2543 RFC 3264 RFC 3264 Indicates if RFC 2543 (set media IP address in SDP to 0.0.0.0) or RFC 3264 (use direction attributes in SDP) is used to put a call on-hold. Allow m&issing Contact header in 200 OK on REGISTER Alt+I Alt+I A 200 OK response on a REGISTER request must contain a Contact header. Some registrars however, do not include a Contact header or include a wrong Contact header. This option allows for such a deviation from the specs. &Max-Forwards header is mandatory Alt+M Alt+M According to RFC 3261 the Max-Forwards header is mandatory. But many implementations do not send this header. If you tick this box, Twinkle will reject a SIP request if Max-Forwards is missing. Put &registration expiry time in contact header Alt+R In a REGISTER message the expiry time for registration can be put in the Contact header or in the Expires header. If you tick this box it will be put in the Contact header, otherwise it goes in the Expires header. &Use compact header names Indicates if compact header names should be used for headers that have a compact form. Allow SDP change during call setup <p>A SIP UAS may send SDP in a 1XX response for early media, e.g. ringing tone. When the call is answered the SIP UAS should send the same SDP in the 200 OK response according to RFC 3261. Once SDP has been received, SDP in subsequent responses should be discarded.</p> <p>By allowing SDP to change during call setup, Twinkle will not discard SDP in subsequent responses and modify the media stream if the SDP is changed. When the SDP in a response is changed, it must have a new version number in the o= line.</p> Use domain &name to create a unique contact header value Alt+N <p> Twinkle creates a unique contact header value by combining the SIP user name and domain: </p> <p> <tt>&nbsp;user_domain@local_ip</tt> </p> <p> This way 2 user profiles, having the same user name but different domain names, have unique contact addresses and hence can be activated simultaneously. </p> <p> Some proxies do not handle a contact header value like this. You can disable this option to get a contact header value like this: </p> <p> <tt>&nbsp;user@local_ip</tt> </p> <p> This format is what most SIP phones use. </p> &Encode Via, Route, Record-Route as list The Via, Route and Record-Route headers can be encoded as a list of comma separated values or as multiple occurrences of the same header. Redirection &Allow redirection Alt+A Alt+B Indicates if Twinkle should redirect a request if a 3XX response is received. Ask user &permission to redirect Indicates if Twinkle should ask the user before redirecting a request when a 3XX response is received. Max re&directions: The number of redirect addresses that Twinkle tries at a maximum before it gives up redirecting a request. This prevents a request from getting redirected forever. SIP extensions disabled inaktiverad supported stöds required krävs preferred föredras Indicates if the 100rel extension (PRACK) is supported:<br><br> <b>disabled</b>: 100rel extension is disabled <br><br> <b>supported</b>: 100rel is supported (it is added in the supported header of an outgoing INVITE). A far-end can now require a PRACK on a 1xx response. <br><br> <b>required</b>: 100rel is required (it is put in the require header of an outgoing INVITE). If an incoming INVITE indicates that it supports 100rel, then Twinkle will require a PRACK when sending a 1xx response. A call will fail when the far-end does not support 100rel. <br><br> <b>preferred</b>: Similar to required, but if a call fails because the far-end indicates it does not support 100rel (420 response) then the call will be re-attempted without the 100rel requirement. &100 rel (PRACK): Replaces Ersätter Indicates if the Replaces-extenstion is supported. REFER REFER Call transfer (REFER) Alt+T Alt+T Indicates if Twinkle should transfer a call if a REFER request is received. As&k user permission to transfer Alt+K Indicates if Twinkle should ask the user before transferring a call when a REFER request is received. Hold call &with referrer while setting up call to transfer target Alt+W Indicates if Twinkle should put the current call on hold when a REFER request to transfer a call is received. Ho&ld call with referee before sending REFER Alt+L Alt+T Indicates if Twinkle should put the current call on hold when you transfer a call. Auto re&fresh subscription to refer event while call transfer is not finished While a call is being transferred, the referee sends NOTIFY messages to the referrer about the progress of the transfer. These messages are only sent for a short interval which length is determined by the referee. If you tick this box, the referrer will automatically send a SUBSCRIBE to lengthen this interval if it is about to expire and the transfer has not yet been completed. Attended refer to AoR (Address of Record) An attended call transfer should use the contact URI as a refer target. A contact URI may not be globally routable however. Alternatively the AoR (Address of Record) may be used. A disadvantage is that the AoR may route to multiple endpoints in case of forking whereas the contact URI routes to a single endoint. Privacy Integritet Privacy options Integritetsalternativ &Send P-Preferred-Identity header when hiding user identity Include a P-Preferred-Identity header with your identity in an INVITE request for a call with identity hiding. NAT traversal NAT-traversering &NAT traversal not needed Choose this option when there is no NAT device between you and your SIP proxy or when your SIP provider offers hosted NAT traversal. &Use statically configured public IP address inside SIP messages Indicates if Twinkle should use the public IP address specified in the next field inside SIP message, i.e. in SIP headers and SDP body instead of the IP address of your network interface.<br><br> When you choose this option you have to create static address mappings in your NAT device as well. You have to map the RTP ports on the public IP address to the same ports on the private IP address of your PC. Choose this option when your SIP provider offers a STUN server for NAT traversal. S&TUN server: S&TUN-server: The hostname, domain name or IP address of the STUN server. Värdnamnet, domännamnet eller IP-adressen för STUN-servern. &Public IP address: &Publik IP-adress: The public IP address of your NAT. Telephone numbers Telefonnummer Only &display user part of URI for telephone number If a URI indicates a telephone number, then only display the user part. E.g. if a call comes in from sip:123456@twinklephone.com then display only "123456" to the user. A URI indicates a telephone number if it contains the "user=phone" parameter or when it has a numerical user part and you ticked the next option. &URI with numerical user part is a telephone number If you tick this option, then Twinkle considers a SIP address that has a user part that consists of digits, *, #, + and special symbols only as a telephone number. In an outgoing message, Twinkle will add the "user=phone" parameter to such a URI. &Remove special symbols from numerical dial strings Telephone numbers are often written with special symbols like dashes and brackets to make them readable to humans. When you dial such a number the special symbols must not be dialed. To allow you to simply copy/paste such a number into Twinkle, Twinkle can remove these symbols when you hit the dial button. &Special symbols: The special symbols that may be part of a telephone number for nice formatting, but must be removed when dialing. Number conversion Match expression Replace Ersätt <p> Often the format of the telphone numbers you need to dial is different from the format of the telephone numbers stored in your address book, e.g. your numbers start with a +-symbol followed by a country code, but your provider expects '00' instead of the '+', or you are at the office and all your numbers need to be prefixed with a '9' to access an outside line. Here you can specify number format conversion using Perl style regular expressions and format strings. </p> <p> For each number you dial, Twinkle will try to find a match in the list of match expressions. For the first match it finds, the number will be replaced with the format string. If no match is found, the number stays unchanged. </p> <p> The number conversion rules are also applied to incoming calls, so the numbers are displayed in the format you want. </p> <h3>Example 1</h3> <p> Assume your country code is 31 and you have stored all numbers in your address book in full international number format, e.g. +318712345678. For dialling numbers in your own country you want to strip of the '+31' and replace it by a '0'. For dialling numbers abroad you just want to replace the '+' by '00'. </p> <p> The following rules will do the trick: </p> <blockquote> <tt> Match expression = \+31([0-9]*) , Replace = 0$1<br> Match expression = \+([0-9]*) , Replace = 00$1</br> </tt> </blockquote> <h3>Example 2</h3> <p> You are at work and all telephone numbers starting with a 0 should be prefixed with a 9 for an outside line. </p> <blockquote> <tt> Match expression = 0[0-9]* , Replace = 9$&<br> </tt> </blockquote> Move the selected number conversion rule upwards in the list. Move the selected number conversion rule downwards in the list. &Add &Lägg till Add a number conversion rule. Re&move Ta &bort Remove the selected number conversion rule. &Edit &Redigera Edit the selected number conversion rule. Type a telephone number here an press the Test button to see how it is converted by the list of number conversion rules. &Test &Testa Test how a number is converted by the number conversion rules. for STUN för STUN When an incoming call is received, this timer is started. If the user answers the call, the timer is stopped. If the timer expires before the user answers the call, then Twinkle will reject the call with a "480 User Not Responding". NAT &keep alive: &No answer: &Inget svar: Ring &back tone: <p> Specify the file name of a .wav file that you want to be played as ring back tone for this user. </p> <p> This ring back tone overrides the ring back tone settings in the system settings. </p> <p> Specify the file name of a .wav file that you want to be played as ring tone for this user. </p> <p> This ring tone overrides the ring tone settings in the system settings. </p> &Ring tone: &Ringsignal: <p> This script is called when you release a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing SIP BYE request are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=local_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the BYE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> This script is called when an incoming call fails. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing SIP failure response are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=in_call_failed</b>. <b>SIPSTATUS_CODE</b> contains the status code of the failure response. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> This script is called when the remote party releases a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming SIP BYE request are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=remote_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the BYE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> You can customize the way Twinkle handles incoming calls. Twinkle can call a script when a call comes in. Based on the output of the script Twinkle accepts, rejects or redirects the call. When accepting the call, the ring tone can be customized by the script as well. The script can be any executable program. </p> <p> <b>Note:</b> Twinkle pauses while your script runs. It is recommended that your script does not take more than 200 ms. When you need more time, you can send the parameters followed by <b>end</b> and keep on running. Twinkle will continue when it receives the <b>end</b> parameter. </p> <p> With your script you can customize call handling by outputting one or more of the following parameters to stdout. Each parameter should be on a separate line. </p> <p> <blockquote> <tt> action=[ continue | reject | dnd | redirect | autoanswer ]<br> reason=&lt;string&gt;<br> contact=&lt;address to redirect to&gt;<br> caller_name=&lt;name of caller to display&gt;<br> ringtone=&lt;file name of .wav file&gt;<br> display_msg=&lt;message to show on display&gt;<br> end<br> </tt> </blockquote> </p> <h2>Parameters</h2> <h3>action</h3> <p> <b>continue</b> - continue call handling as usual<br> <b>reject</b> - reject call<br> <b>dnd</b> - deny call with do not disturb indication<br> <b>redirect</b> - redirect call to address specified by <b>contact</b><br> <b>autoanswer</b> - automatically answer a call<br> </p> <p> When the script does not write an action to stdout, then the default action is continue. </p> <p> <b>reason: </b> With the reason parameter you can set the reason string for reject or dnd. This might be shown to the far-end user. </p> <p> <b>caller_name: </b> This parameter will override the display name of the caller. </p> <p> <b>ringtone: </b> The ringtone parameter specifies the .wav file that will be played as ring tone when action is continue. </p> <h2>Environment variables</h2> <p> The values of all SIP headers in the incoming INVITE message are passed in environment variables to your script. The variable names are formatted as <b>SIP_&lt;HEADER_NAME&gt;</b> E.g. SIP_FROM contains the value of the from header. </p> <p> TWINKLE_TRIGGER=in_call. SIPREQUEST_METHOD=INVITE. The request-URI of the INVITE will be passed in <b>SIPREQUEST_URI</b>. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> This script is called when the remote party answers your call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming 200 OK are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> This script is called when you answer an incoming call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing 200 OK are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=in_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. Call released locall&y: <p> This script is called when an outgoing call fails. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming SIP failure response are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call_failed</b>. <b>SIPSTATUS_CODE</b> contains the status code of the failure response. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> This script is called when you make a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing INVITE are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call</b>. <b>SIPREQUEST_METHOD=INVITE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the INVITE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. Outgoing call a&nswered: Incoming call &failed: &Incoming call: &Inkommande samtal: Call released &remotely: Incoming call &answered: O&utgoing call: &Utgående samtal: Out&going call failed: &Enable ZRTP/SRTP encryption &Aktivera ZRTP/SRTP-kryptering When ZRTP/SRTP is enabled, then Twinkle will try to encrypt the audio of each call you originate or receive. Encryption will only succeed if the remote party has ZRTP/SRTP support enabled. If the remote party does not support ZRTP/SRTP, then the audio channel will stay unecrypted. ZRTP settings Inställningar för ZRTP O&nly encrypt audio if remote party indicated ZRTP support in SDP A SIP endpoint supporting ZRTP may indicate ZRTP support during call setup in its signalling. Enabling this option will cause Twinkle only to encrypt calls when the remote party indicates ZRTP support. &Indicate ZRTP support in SDP Twinkle will indicate ZRTP support during call setup in its signalling. &Popup warning when remote party disables encryption during call A remote party of an encrypted call may send a ZRTP go-clear command to stop encryption. When Twinkle receives this command it will popup a warning if this option is enabled. &Voice mail address: The SIP address or telephone number to access your voice mail. Unsollicited Sollicited <H2>Message waiting indication type</H2> <p> If your provider offers the message waiting indication service, then Twinkle can show you when new voice mail messages are waiting. Ask your provider which type of message waiting indication is offered. </p> <H3>Unsollicited</H3> <p> Asterisk provides unsollicited message waiting indication. </p> <H3>Sollicited</H3> <p> Sollicited message waiting indication as specified by RFC 3842. </p> &MWI type: Sollicited MWI Subscription &duration: Mailbox &user name: The hostname, domain name or IP address of your voice mailbox server. For sollicited MWI, an endpoint subscribes to the message status for a limited duration. Just before the duration expires, the endpoint should refresh the subscription. Your user name for accessing your voice mailbox. Mailbox &server: Via outbound &proxy Check this option if Twinkle should send SIP messages to the mailbox server via the outbound proxy. Dynamic payload type %1 is used more than once. You must fill in a user name for your SIP account. Du måste fylla i ett användarnamn för ditt SIP-konto. You must fill in a domain name for your SIP account. This could be the hostname or IP address of your PC if you want direct PC to PC dialing. Du måste fylla i ett domännamn för ditt SIP-konto. Detta kan vara värdnamnet eller IP-adressen för din dator, om du vill ha direktsamtal, PC till PC. Invalid domain. Ogiltig domän. Invalid user name. Ogiltigt användarnamn. Invalid value for registrar. Invalid value for outbound proxy. You must fill in a mailbox user name. You must fill in a mailbox server Invalid mailbox server. Invalid mailbox user name. Value for public IP address missing. Invalid value for STUN server. Ogiltigt värde för STUN-server. Ring tones Description of .wav files in file dialog Ringsignaler Choose ring tone Välj ringsignal Ring back tones Description of .wav files in file dialog All files Alla filer Choose incoming call script Choose incoming call answered script Choose incoming call failed script Choose outgoing call script Choose outgoing call answered script Choose outgoing call failed script Choose local release script Choose remote release script Instant message Snabbmeddelande Presence Närvaro Transport/NAT Transport/NAT AKA AM&F AKA AM&F A&KA OP: A&KA OP: Authentication management field for AKAv1-MD5 authentication. Operator variant key for AKAv1-MD5 authentication. Add q-value to registration The q-value indicates the priority of your registered device. If besides Twinkle you register other SIP devices for this account, then the network may use these values to determine which device to try first when delivering a call. The q-value is a value between 0.000 and 1.000. A higher value means a higher priority. SIP transport SIP-transport UDP UDP TCP TCP Transport mode for SIP. In auto mode, the size of a message determines which transport protocol is used. Messages larger than the UDP threshold are sent via TCP. Smaller messages are sent via UDP. T&ransport protocol: T&ransportprotokoll: UDP t&hreshold: bytes byte Messages larger than the threshold are sent via TCP. Smaller messages are sent via UDP. Use &STUN (does not work for incoming TCP) P&ersistent TCP connection Keep the TCP connection established during registration open such that the SIP proxy can reuse this connection to send incoming requests. Application ping packets are sent to test if the connection is still alive. Use tel-URI for telephone &number Expand a dialed telephone number to a tel-URI instead of a sip-URI. Select ring back tone file. Select ring tone file. Select script file. &Maximum number of sessions: When you have this number of instant message sessions open, new incoming message sessions will be rejected. &Send composing indications when typing a message. Twinkle sends a composing indication when you type a message. This way the recipient can see that you are typing. Your presence Din närvaro &Publish availability at startup &Publicera tillgänglighet vid uppstart Publish your availability at startup. Publicera din tillgänglighet vid uppstart. Publication &refresh interval (sec): Refresh rate of presence publications. Buddy presence &Subscription refresh interval (sec): Refresh rate of presence subscriptions. %1 converts to %2 %1 konverteras till %2 AKA AM&F: Prepr&ocessing Preprocessing (improves quality at remote end) &Automatic gain control Automatic gain control (AGC) is a feature that deals with the fact that the recording volume may vary by a large amount between different setups. The AGC provides a way to adjust a signal to a reference volume. This is useful because it removes the need for manual adjustment of the microphone gain. A secondary advantage is that by setting the microphone gain to a conservative (low) level, it is easier to avoid clipping. Automatic gain control &level: Automatic gain control level represents percentual value of automatic gain setting of a microphone. Recommended value is about 25%. &Voice activity detection When enabled, voice activity detection detects whether the input signal represents a speech or a silence/background noise. &Noise reduction The noise reduction can be used to reduce the amount of background noise present in the input signal. This provides higher quality speech. Acoustic &Echo Cancellation In any VoIP communication, if a speech from the remote end is played in the local loudspeaker, then it propagates in the room and is captured by the microphone. If the audio captured from the microphone is sent directly to the remote end, then the remote user hears an echo of his voice. An acoustic echo cancellation is designed to remove the acoustic echo before it is sent to the remote end. It is important to understand that the echo canceller is meant to improve the quality on the remote end. Variable &bit-rate Discontinuous &Transmission &Quality: Speex is a lossy codec, which means that it achives compression at the expense of fidelity of the input speech signal. Unlike some other speech codecs, it is possible to control the tradeoff made between quality and bit-rate. The Speex encoding process is controlled most of the time by a quality parameter that ranges from 0 to 10. Accept call &transfer request (incoming REFER) Allow call transfer while consultation in progress When you perform an attended call transfer, you normally transfer the call after you established a consultation call. If you enable this option you can transfer the call while the consultation call is still in progress. This is a non-standard implementation and may not work with all SIP devices. bytes Enable NAT &keep alive Send UDP NAT keep alive packets. If you have enabled STUN or NAT keep alive, then Twinkle will send keep alive packets at this interval rate to keep the address bindings in your NAT device alive. WizardForm Twinkle - Wizard Twinkle - Guide The hostname, domain name or IP address of the STUN server. Värdnamnet, domännamnet eller IP-adressen för STUN-servern. S&TUN server: S&TUN-server: The SIP user name given to you by your provider. It is the user part in your SIP address, <b>username</b>@domain.com This could be a telephone number. <br><br> This field is mandatory. SIP-användarnamnet som din leverantör levererat till dig. Detta är användardelen av din SIP-adress, <b>användarnamn</b>@domän.se. Detta kan vara ett telefonnummer. <br><br> Detta fält är obligatoriskt. &Domain*: &Domän*: Choose your SIP service provider. If your SIP service provider is not in the list, then select <b>Other</b> and fill in the settings you received from your provider.<br><br> If you select one of the predefined SIP service providers then you only have to fill in your name, user name, authentication name and password. Välj din SIP-tjänsteleverantör. Om din SIP-tjänsteleverantör inte finns med i listan så ska du välja <b>Annan</b> och fylla i inställningarna som du fått från din leverantör.<br><br> Om du väljer en av de fördefinierade SIP-tjänsteleverantörerna så behöver du endast fylla i ditt namn, användarnamn, autentiseringsnamn och lösenord. &Authentication name: &Autentiseringsnamn: &Your name: &Ditt namn: Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Ditt SIP-autentiseringsnamn. Ganska ofta är detta samma som ditt SIP-användarnamn. Det kan dock skilja sig. The domain part of your SIP address, username@<b>domain.com</b>. Instead of a real domain this could also be the hostname or IP address of your <b>SIP proxy</b>. If you want direct IP phone to IP phone communications then you fill in the hostname or IP address of your computer. <br><br> This field is mandatory. Domändelen av din SIP-adress, användarnamn@<b>domän.se</b>. Istället för en riktig domän så kan detta även vara värdnamnet eller IP-adressen för din <b>SIP-proxy</b>. Om du vill använda direktkommunikation mellan IP-telefon och IP-telefon så kan du fylla i värdnamnet eller IP-adress för din dator. <br><br> Detta fält är obligatoriskt. This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. Detta är helt enkelt ditt fullständiga namn, t.ex. Sven Svensson. Det används endast för visning. När du ringer ett samtal kan dock detta namn visas för motparten. SIP pro&xy: SIP-pro&xy: The hostname, domain name or IP address of your SIP proxy. If this is the same value as your domain, you may leave this field empty. Värdnamnet, domännamnet eller IP-adressen för din SIP-proxy. Om detta är samma värde som för din domän så kan du lämna detta fält tomt. &SIP service provider: &SIP-tjänsteleverantör: &Password: &Lösenord: &User name*: A&nvändarnamn*: Your password for authentication. Ditt lösenord för autentisering. &OK &OK Alt+O Alt+O &Cancel &Avbryt Alt+C Alt+A None (direct IP to IP calls) Ingen (direktsamtal IP till IP) Other Annan User profile wizard: Guide för användarprofil: You must fill in a user name for your SIP account. Du måste fylla i ett användarnamn för ditt SIP-konto. You must fill in a domain name for your SIP account. This could be the hostname or IP address of your PC if you want direct PC to PC dialing. Du måste fylla i ett domännamn för ditt SIP-konto. Detta kan vara värdnamnet eller IP-adressen för din dator, om du vill ha direktsamtal, PC till PC. Invalid value for SIP proxy. Ogiltigt värde för SIP-proxy. Invalid value for STUN server. Ogiltigt värde för STUN-server. YesNoDialog &Yes &Ja &No &Nej incoming_call Answer Svara Reject Avvisa twinkle-1.10.1/src/gui/lang/twinkle_xx.ts000066400000000000000000006256431277565361200203450ustar00rootroot00000000000000 AddressCardForm Twinkle - Address Card &Remark: Infix name of contact. First name of contact. &First name: You may place any remark about the contact here. &Phone: &Infix name: Phone number or SIP address of contact. Last name of contact. &Last name: &OK Alt+O &Cancel Alt+C You must fill in a name. You must fill in a phone number or SIP address. AddressTableModel Name Phone Remark AuthenticationForm Twinkle - Authentication user No need to translate The user for which authentication is requested. profile No need to translate The user profile of the user for which authentication is requested. User profile: User: &Password: Your password for authentication. Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. &User name: &OK &Cancel Login required for realm: realm No need to translate The realm for which you need to authenticate. BuddyForm Twinkle - Buddy Address book Select an address from the address book. &Phone: Name of your buddy. &Show availability Alt+S Check this option if you want to see the availability of your buddy. This will only work if your provider offers a presence agent. &Name: SIP address your buddy. &OK Alt+O &Cancel Alt+C You must fill in a name. Invalid phone. Failed to save buddy list: %1 BuddyList Availability unknown offline online request failed request rejected not published failed to publish Click right to add a buddy. CoreAudio Failed to open sound card Failed to create a UDP socket (RTP) on port %1 Failed to create audio receiver thread. Failed to create audio transmitter thread. CoreCallHistory local user remote user failure unknown in out DeregisterForm Twinkle - Deregister deregister all devices &OK &Cancel DiamondcardProfileForm Fill in your account ID. Fill in your PIN code. A user profile with name %1 already exists. DtmfForm Twinkle - DTMF Keypad 2 3 Over decadic A. Normally not needed. 4 5 6 Over decadic B. Normally not needed. 7 8 9 Over decadic C. Normally not needed. Star (*) 0 Pound (#) Over decadic D. Normally not needed. 1 &Close Alt+C GUI Failed to create a %1 socket (SIP) on port %2 Override lock file and start anyway? The following profiles are both for user %1 You can only run multiple profiles for different users. If these are users for different domains, then enable the following option in your user profile (SIP protocol) Use domain name to create a unique contact header Cannot find a network interface. Twinkle will use 127.0.0.1 as the local IP address. When you connect to the network you have to restart Twinkle to use the correct IP address. Line %1: incoming call for %2 Call transferred by %1 Line %1: far end cancelled call. Line %1: far end released call. Line %1: SDP answer from far end not supported. Line %1: SDP answer from far end missing. Line %1: Unsupported content type in answer from far end. Line %1: no ACK received, call will be terminated. Line %1: no PRACK received, call will be terminated. Line %1: PRACK failed. Line %1: failed to cancel call. Line %1: far end answered call. Line %1: call failed. The call can be redirected to: Line %1: call released. Line %1: call established. Response on terminal capability request: %1 %2 Terminal capabilities of %1 Accepted body types: unknown Accepted encodings: Accepted languages: Allowed requests: Supported extensions: none End point type: Line %1: call retrieve failed. %1, registration failed: %2 %3 %1, registration succeeded (expires = %2 seconds) %1, registration failed: STUN failure %1, de-registration succeeded: %2 %3 %1, de-registration failed: %2 %3 %1, fetching registrations failed: %2 %3 : you are not registered : you have the following registrations : fetching registrations... Line %1: redirecting request to Redirecting request to: %1 Line %1: DTMF detected: invalid DTMF telephone event (%1) Line %1: send DTMF %2 Line %1: far end does not support DTMF telephone events. Line %1: received notification. Event: %1 State: %1 Reason: %1 Progress: %1 %2 Line %1: call transfer failed. Line %1: call successfully transferred. Line %1: call transfer still in progress. No further notifications will be received. Line %1: transferring call to %2 Transfer requested by %1 Line %1: Call transfer failed. Retrieving original call. %1, STUN request failed: %2 %3 %1, STUN request failed. Redirecting call User profile: User: Do you allow the call to be redirected to the following destination? If you don't want to be asked this anymore, then you must change the settings in the SIP protocol section of the user profile. Redirecting request Do you allow the %1 request to be redirected to the following destination? Transferring call Request to transfer call received from: Request to transfer call received. Do you allow the call to be transferred to the following destination? Info: Warning: Critical: Firewall / NAT discovery... Abort Line %1 Click the padlock to confirm a correct SAS. The remote user on line %1 disabled the encryption. Line %1: SAS confirmed. Line %1: SAS confirmation reset. %1, voice mail status failure. %1, voice mail status rejected. %1, voice mailbox does not exist. %1, voice mail status terminated. Accepted by network Line %1: call rejected. Line %1: call redirected. Failed to start conference. Failed to save message attachment: %1 Transferred by: %1 Cannot open web browser: %1 Configure your web browser in the system settings. GetAddressForm Twinkle - Select address &KAddressBook This list of addresses is taken from <b>KAddressBook</b>. Contacts for which you did not provide a phone number are not shown here. To add, delete or modify address information you have to use KAddressBook. &Show only SIP addresses Alt+S Check this option when you only want to see contacts with SIP addresses, i.e. starting with "<b>sip:</b>". &Reload Alt+R Reload the list of addresses from KAddressbook. &Local address book Contacts in the local address book of Twinkle. &Add Alt+A Add a new contact to the local address book. &Delete Alt+D Delete a contact from the local address book. &Edit Alt+E Edit a contact from the local address book. &OK Alt+O &Cancel Alt+C <p>You seem not to have any contacts with a phone number in <b>KAddressBook</b>, KDE's address book application. Twinkle retrieves all contacts with a phone number from KAddressBook. To manage your contacts you have to use KAddressBook.<p>As an alternative you may use Twinkle's local address book.</p> GetProfileNameForm Twinkle - Profile name &OK &Cancel Enter a name for your profile: <b>The name of your profile</b> <br><br> A profile contains your user settings, e.g. your user name and password. You have to give each profile a name. <br><br> If you have multiple SIP accounts, you can create multiple profiles. When you startup Twinkle it will show you the list of profile names from which you can select the profile you want to run. <br><br> To remember your profiles easily you could use your SIP user name as a profile name, e.g. <b>example@example.com</b> Cannot find .twinkle directory in your home directory. Profile already exists. Rename profile '%1' to: HistoryForm Twinkle - Call History Time In/Out From/To Subject Status Call details Details of the selected call record. View &Incoming calls Alt+I Check this option to show incoming calls. &Outgoing calls Alt+O Check this option to show outgoing calls. &Answered calls Alt+A Check this option to show answered calls. &Missed calls Alt+M Check this option to show missed calls. Current &user profiles only Alt+U Check this option to show only calls associated with this user profile. C&lear Alt+L <p>Clear the complete call history.</p> <p><b>Note:</b> this will clear <b>all</b> records, also records not shown depending on the checked view options.</p> Clo&se Alt+S Close this window. &Call Alt+C Call selected address. Call... Delete Call start: Call answer: Call end: Call duration: Direction: From: To: Reply to: Referred by: Subject: Released by: Status: Far end device: User profile: conversation Re: Number of calls: ### Total call duration: IncomingCallPopup %1 calling InviteForm Twinkle - Call &To: Optionally you can provide a subject here. This might be shown to the callee. Address book Select an address from the address book. The address that you want to call. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. The user that will make the call. &Subject: &From: &Hide identity Alt+H <p> With this option you request your SIP provider to hide your identity from the called party. This will only hide your identity, e.g. your SIP address, telephone number. It does <b>not</b> hide your IP address. </p> <p> <b>Warning:</b> not all providers support identity hiding. </p> &OK &Cancel Not all SIP providers support identity hiding. Make sure your SIP provider supports it if you really need it. F10 LogViewForm Twinkle - Log Contents of the current log file (~/.twinkle/twinkle.log) &Close Alt+C C&lear Alt+L Clear the log window. This does <b>not</b> clear the log file itself. MessageForm Twinkle - Instant message &To: The user that will send the message. The address of the user that you want to send a message. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Address book Select an address from the address book. &User profile: Conversation Type your message here and then press "send" to send it. &Send Alt+S Send the message. Delivery failure Delivery notification Send file... Send file image size is scaled down in preview Open with %1... Open with... Save attachment as... File already exists. Do you want to overwrite this file? Failed to save attachment. %1 is typing a message. F10 Size MessageFormView sending message MphoneForm Twinkle Buddy list You can create a separate buddy list for each user profile. You can only see availability of your buddies and publish your own availability if your provider offers a presence server. &Call: Label in front of combobox to enter address The address that you want to call. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Address book Select an address from the address book. Dial Dial the address. &User: The user that will make the call. Auto answer indication. Call redirect indication. Do not disturb indication. Message waiting indication. Missed call indication. Registration status. Display Line status Line &1: Alt+1 Click to switch to line 1. From: To: Subject: idle No need to translate Transferring call sas No need to translate Short authentication string g711a/g711a No need to translate Audio codec 0:00:00 Call duration sip:from No need to translate sip:to No need to translate subject No need to translate photo No need to translate Line &2: Alt+2 Click to switch to line 2. &File &Edit C&all Activate line &Message &Registration &Services &View &Help Quit &Quit Ctrl+Q About Twinkle &About Twinkle Call someone F5 Answer incoming call F6 Release call Esc Reject incoming call F8 Put a call on hold, or retrieve a held call Redirect incoming call without answering Open keypad to enter digits for voice menu's Register &Register Deregister &Deregister Deregister this device Show registrations &Show registrations Terminal capabilities Request terminal capabilities from someone Do not disturb &Do not disturb Call redirection Call &redirection... Repeat last call F12 About Qt About &Qt User profile &User profile... Join two calls in a 3-way conference Mute a call Transfer call System settings &System settings... Deregister all Deregister &all Deregister all your registered devices Auto answer &Auto answer Log &Log... Call history Call &history... F9 Change user ... &Change user ... Activate or de-activate users What's This? What's &This? Shift+F1 Line 1 Line 2 &Display Voice mail &Voice mail Access voice mail F11 Msg Instant &message... Instant message &Buddy list &Call... &Edit... &Delete O&ffline &Online &Change availability &Add buddy... idle dialing attempting call, please wait incoming call establishing call, please wait established established (waiting for media) releasing call, please wait unknown state Voice is encrypted Click to confirm SAS. Click to clear SAS verification. Transfer consultation User: Call: Hide identity Registration status: Registered Failed Not registered Click to show registrations. No users are registered. %1 new, 1 old message %1 new, %2 old messages 1 new message %1 new messages 1 old message %1 old messages Messages waiting No messages <b>Voice mail status:</b> Failure Unknown Click to access voice mail. Do not disturb active for: Redirection active for: Auto answer active for: Click to activate/deactivate Click to activate Do not disturb is not active. Redirection is not active. Auto answer is not active. Click to see call history for details. You have no missed calls. You missed 1 call. You missed %1 calls. Starting user profiles... The following profiles are both for user %1 You can only run multiple profiles for different users. You have changed the SIP UDP port. This setting will only become active when you restart Twinkle. not provisioned You must provision your voice mail address in your user profile, before you can access it. The line is busy. Cannot access voice mail. The voice mail address %1 is an invalid address. Please provision a valid address in your user profile. Failed to save buddy list: %1 F10 Diamondcard Manual &Manual Sign up &Sign up... Recharge... Balance history... Call history... Admin center... Recharge Balance history Admin center Call &Answer Answer &Bye Bye &Reject Reject &Hold Hold R&edirect... Redirect &Dtmf... Dtmf &Terminal capabilities... &Redial Redial &Conference Conf &Mute Mute Trans&fer... Xfer NumberConversionForm Twinkle - Number conversion &Match expression: &Replace: Perl style format string for the replacement number. Perl style regular expression matching the number format you want to modify. &OK Alt+O &Cancel Alt+C Match expression may not be empty. Replace value may not be empty. Invalid regular expression. RedirectForm Twinkle - Redirect Redirect incoming call to You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. &3rd choice destination: &2nd choice destination: &1st choice destination: Address book Select an address from the address book. &OK &Cancel F10 F12 F11 SelectNicForm Twinkle - Select NIC Select the network interface/IP address that you want to use: You have multiple IP addresses. Here you must select which IP address should be used. This IP address will be used inside the SIP messages. Set as default &IP Alt+I Make the selected IP address the default IP address. The next time you start Twinkle, this IP address will be automatically selected. Set as default &NIC Alt+N Make the selected network interface the default interface. The next time you start Twinkle, this interface will be automatically selected. &OK Alt+O If you want to remove or change the default at a later time, you can do that via the system settings. SelectProfileForm Twinkle - Select user profile Select user profile(s) to run: Tick the check boxes of the user profiles that you want to run and press run. Create a new profile with the profile editor. &Wizard Alt+W Create a new profile with the wizard. &Edit Alt+E Edit the highlighted profile. &Delete Alt+D Delete the highlighted profile. Ren&ame Alt+A Rename the highlighted profile. &Set as default Alt+S Make the selected profiles the default profiles. The next time you start Twinkle, these profiles will be automatically run. &Run Alt+R Run Twinkle with the selected profiles. S&ystem settings Alt+Y Edit the system settings. &Cancel Alt+C <html>Before you can use Twinkle, you must create a user profile.<br>Click OK to create a profile.</html> &Profile editor <html>Next you may adjust the system settings. You can change these settings always at a later time.<br><br>Click OK to view and adjust the system settings.</html> You did not select any user profile to run. Please select a profile. Are you sure you want to delete profile '%1'? Delete profile Failed to delete profile. Failed to rename profile. <p>If you want to remove or change the default at a later time, you can do that via the system settings.</p> Cannot find .twinkle directory in your home directory. Create profile Ed&itor Alt+I Dia&mondcard Alt+M Modify profile Startup profile &Diamondcard Create a profile for a Diamondcard account. With a Diamondcard account you can make worldwide calls to regular and cell phones and send SMS messages. <html>You can use the profile editor to create a profile. With the profile editor you can change many settings to tune the SIP protocol, RTP and many other things.<br><br>Alternatively you can use the wizard to quickly setup a user profile. The wizard asks you only a few essential settings. If you create a user profile with the wizard you can still edit the full profile with the profile editor at a later time.<br><br> You can create a Diamondcard account to make worldwide calls to regular and cell phones and send SMS messages.<br><br> Choose what method you wish to use.</html> SelectUserForm Twinkle - Select user &Cancel Alt+C &Select all Alt+S &OK Alt+O C&lear all Alt+L purpose No need to translate Register Select users that you want to register. Deregister Select users that you want to deregister. Deregister all devices Select users for which you want to deregister all devices. Do not disturb Select users for which you want to enable 'do not disturb'. Auto answer Select users for which you want to enable 'auto answer'. SendFileForm Twinkle - Send File Select file to send. &File: &Subject: &OK Alt+O &Cancel Alt+C File does not exist. Send file... SrvRedirectForm Twinkle - Call Redirection User: There are 3 redirect services:<p> <b>Unconditional:</b> redirect all calls </p> <p> <b>Busy:</b> redirect a call if both lines are busy </p> <p> <b>No answer:</b> redirect a call when the no-answer timer expires </p> &Unconditional &Redirect all calls Alt+R Activate the unconditional redirection service. Redirect to You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. &3rd choice destination: &2nd choice destination: &1st choice destination: Address book Select an address from the address book. &Busy &Redirect calls when I am busy Activate the redirection when busy service. &No answer &Redirect calls when I do not answer Activate the redirection on no answer service. &OK Alt+O Accept and save all changes. &Cancel Alt+C Undo your changes and close the window. You have entered an invalid destination. F10 F11 F12 SysSettingsForm Twinkle - System Settings General Audio Ring tones Address book Network Log Select a category for which you want to see or modify the settings. &OK Alt+O Accept and save your changes. &Cancel Alt+C Undo all your changes and close the window. Sound Card Select the sound card for playing the ring tone for incoming calls. Select the sound card to which your microphone is connected. Select the sound card for the speaker function during a call. &Speaker: &Ring tone: Other device: &Microphone: &Validate devices before usage Alt+V <p> Twinkle validates the audio devices before usage to avoid an established call without an audio channel. <p> On startup of Twinkle a warning is given if an audio device is inaccessible. <p> If before making a call, the microphone or speaker appears to be invalid, a warning is given and no call can be made. <p> If before answering a call, the microphone or speaker appears to be invalid, a warning is given and the call will not be answered. Advanced OSS &fragment size: 16 32 64 128 256 The ALSA play period size influences the real time behaviour of your soundcard for playing sound. If your sound frequently drops while using ALSA, you might try a different value here. ALSA &play period size: &ALSA capture period size: The OSS fragment size influences the real time behaviour of your soundcard. If your sound frequently drops while using OSS, you might try a different value here. The ALSA capture period size influences the real time behaviour of your soundcard for capturing sound. If the other side of your call complains about frequently dropping sound, you might try a different value here. &Max log size: The maximum size of a log file in MB. When the log file exceeds this size, a backup of the log file is created and the current log file is zapped. Only one backup log file will be kept. MB Log &debug reports Alt+D Indicates if reports marked as "debug" will be logged. Log &SIP reports Alt+S Indicates if SIP messages will be logged. Log S&TUN reports Alt+T Indicates if STUN messages will be logged. Log m&emory reports Alt+E Indicates if reports concerning memory management will be logged. System tray Create &system tray icon on startup Enable this option if you want a system tray icon for Twinkle. The system tray icon is created when you start Twinkle. &Hide in system tray when closing main window Alt+H Enable this option if you want Twinkle to hide in the system tray when you close the main window. Startup S&tartup hidden in system tray Next time you start Twinkle it will immediately hide in the system tray. This works best when you also select a default user profile. If you always use the same profile(s), then you can mark these profiles as default here. The next time you start Twinkle, you will not be asked to select which profiles to run. The default profiles will automatically run. Services Call &waiting Alt+W With call waiting an incoming call is accepted when only one line is busy. When you disable call waiting an incoming call will be rejected when one line is busy. Hang up &both lines when ending a 3-way conference call. Alt+B Hang up both lines when you press bye to end a 3-way conference call. When this option is disabled, only the active line will be hung up and you can continue talking with the party on the other line. &Maximum calls in call history: The maximum number of calls that will be kept in the call history. &Auto show main window on incoming call after Alt+A When the main window is hidden, it will be automatically shown on an incoming call after the number of specified seconds. Number of seconds after which the main window should be shown. secs Maximum allowed size (0-65535) in bytes of an incoming SIP message over UDP. &SIP port: &RTP port: Max. SIP message size (&TCP): The UDP/TCP port used for sending and receiving SIP messages. Max. SIP message size (&UDP): Maximum allowed size (0-4294967295) in bytes of an incoming SIP message over TCP. The UDP port used for sending and receiving RTP for the first line. The UDP port for the second line is 2 higher. E.g. if port 8000 is used for the first line, then the second line uses port 8002. When you use call transfer then the next even port (eg. 8004) is also used. Ring tone &Play ring tone on incoming call Alt+P Indicates if a ring tone should be played when a call comes in. &Default ring tone Play the default ring tone when a call comes in. C&ustom ring tone Alt+U Play a custom ring tone when a call comes in. Specify the file name of a .wav file that you want to be played as ring tone. Select ring tone file. Ring back tone P&lay ring back tone when network does not play ring back tone Alt+L <p> Play ring back tone while you are waiting for the far-end to answer your call. </p> <p> Depending on your SIP provider the network might provide ring back tone or an announcement. </p> D&efault ring back tone Play the default ring back tone. Cu&stom ring back tone Play a custom ring back tone. Specify the file name of a .wav file that you want to be played as ring back tone. Select ring back tone file. &Lookup name for incoming call On an incoming call, Twinkle will try to find the name belonging to the incoming SIP address in your address book. This name will be displayed. Ove&rride received display name Alt+R The caller may have provided a display name already. Tick this box if you want to override that name with the name you have in your address book. Lookup &photo for incoming call Lookup the photo of a caller in your address book and display it on an incoming call. Ring tones Description of .wav files in file dialog Choose ring tone Ring back tones Description of .wav files in file dialog Choose ring back tone W&eb browser command: Command to start your web browser. If you leave this field empty Twinkle will try to figure out your default web browser. 512 1024 Tip: for crackling sound with PulseAudio, set play period size to maximum. Enable in-call OSD SysTrayPopup Answer Reject Incoming Call TermCapForm Twinkle - Terminal Capabilities &From: Get terminal capabilities of &To: The address that you want to query for capabilities (OPTION request). This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Address book Select an address from the address book. &OK &Cancel F10 TransferForm Twinkle - Transfer Transfer call to &To: The address of the person you want to transfer the call to. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Address book Select an address from the address book. &Blind transfer Alt+B Transfer the call to a third party without contacting that third party yourself. T&ransfer with consultation Alt+R Before transferring the call to a third party, first consult the party yourself. Transfer to other &line Alt+L Connect the remote party on the active line with the remote party on the other line. &OK Alt+O &Cancel F10 TwinkleCore Anonymous Warning: Failed to create log file %1 . Cannot open file for reading: %1 File system error while reading file %1 . Cannot open file for writing: %1 File system error while writing file %1 . Excessive number of socket errors. Built with support for: Contributions: This software contains the following software from 3rd parties: * GSM codec from Jutta Degener and Carsten Bormann, University of Berlin * G.711/G.726 codecs from Sun Microsystems (public domain) * iLBC implementation from RFC 3951 (www.ilbcfreeware.org) * Parts of the STUN project at http://sourceforge.net/projects/stun * Parts of libsrv at http://libsrv.sourceforge.net/ For RTP the following dynamic libraries are linked: Translated to english by <your name> Directory %1 does not exist. Cannot open file %1 . %1 is not set to your home directory. Directory %1 (%2) does not exist. Cannot create directory %1 . %1 is already running. Lock file %2 already exists. Cannot create %1 . Syntax error in file %1 . Failed to backup %1 to %2 unknown name (device is busy) Default device Cannot access the ring tone device (%1). Cannot access the speaker (%1). Cannot access the microphone (%1). Cannot receive incoming TCP connections. Call transfer - %1 Sound card cannot be set to full duplex. Cannot set buffer size on sound card. Sound card cannot be set to %1 channels. Cannot set sound card to 16 bits recording. Cannot set sound card to 16 bits playing. Cannot set sound card sample rate to %1 Opening ALSA driver failed Cannot open ALSA driver for PCM playback Cannot open ALSA driver for PCM capture Cannot resolve STUN server: %1 You are behind a symmetric NAT. STUN will not work. Configure a public IP address in the user profile and create the following static bindings (UDP) in your NAT. public IP: %1 --> private IP: %2 (SIP signaling) public IP: %1-%2 --> private IP: %3-%4 (RTP/RTCP) Cannot reach the STUN server: %1 If you are behind a firewall then you need to open the following UDP ports. Port %1 (SIP signaling) Ports %1-%2 (RTP/RTCP) NAT type discovery via STUN failed. Failed to create file %1 Failed to write data to file %1 Failed to send message. Cannot lock %1 . UserProfileForm Twinkle - User Profile User profile: Select which profile you want to edit. User SIP server Voice mail Instant message Presence RTP audio SIP protocol Transport/NAT Address format Timers Ring tones Scripts Security Select a category for which you want to see or modify the settings. &OK Alt+O Accept and save your changes. &Cancel Alt+C Undo all your changes and close the window. SIP account &User name*: &Domain*: Or&ganization: The SIP user name given to you by your provider. It is the user part in your SIP address, <b>username</b>@domain.com This could be a telephone number. <br><br> This field is mandatory. The domain part of your SIP address, username@<b>domain.com</b>. Instead of a real domain this could also be the hostname or IP address of your <b>SIP proxy</b>. If you want direct IP phone to IP phone communications then you fill in the hostname or IP address of your computer. <br><br> This field is mandatory. You may fill in the name of your organization. When you make a call, this might be shown to the called party. This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. &Your name: SIP authentication &Realm: Authentication &name: &Password: The realm for authentication. This value must be provided by your SIP provider. If you leave this field empty, then Twinkle will try the user name and password for any realm that it will be challenged with. Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. Your password for authentication. Registrar &Registrar: The hostname, domain name or IP address of your registrar. If you use an outbound proxy that is the same as your registrar, then you may leave this field empty and only fill in the address of the outbound proxy. &Expiry: The registration expiry time that Twinkle will request. seconds Re&gister at startup Alt+G Indicates if Twinkle should automatically register when you run this user profile. You should disable this when you want to do direct IP phone to IP phone communication without a SIP proxy. Add q-value to registration The q-value indicates the priority of your registered device. If besides Twinkle you register other SIP devices for this account, then the network may use these values to determine which device to try first when delivering a call. The q-value is a value between 0.000 and 1.000. A higher value means a higher priority. Outbound Proxy &Use outbound proxy Alt+U Indicates if Twinkle should use an outbound proxy. If an outbound proxy is used then all SIP requests are sent to this proxy. Without an outbound proxy, Twinkle will try to resolve the SIP address that you type for a call invitation for example to an IP address and send the SIP request there. Outbound &proxy: &Send in-dialog requests to proxy Alt+S SIP requests within a SIP dialog are normally sent to the address in the contact-headers exchanged during call setup. If you tick this box, that address is ignored and in-dialog request are also sent to the outbound proxy. &Don't send a request to proxy if its destination can be resolved locally. Alt+D When you tick this option Twinkle will first try to resolve a SIP address to an IP address itself. If it can, then the SIP request will be sent there. Only when it cannot resolve the address, it will send the SIP request to the proxy (note that an in-dialog request will only be sent to the proxy in this case when you also ticked the previous option.) The hostname, domain name or IP address of your outbound proxy. Co&decs Codecs Available codecs: G.711 A-law G.711 u-law GSM speex-nb (8 kHz) speex-wb (16 kHz) speex-uwb (32 kHz) List of available codecs. Move a codec from the list of available codecs to the list of active codecs. Move a codec from the list of active codecs to the list of available codecs. Active codecs: List of active codecs. These are the codecs that will be used for media negotiation during call setup. The order of the codecs is the order of preference of use. Move a codec upwards in the list of active codecs, i.e. increase its preference of use. Move a codec downwards in the list of active codecs, i.e. decrease its preference of use. &G.711/G.726 payload size: The preferred payload size for the G.711 and G.726 codecs. ms &Follow codec preference from far end on incoming calls Alt+F <p> For incoming calls, follow the preference from the far-end (SDP offer). Pick the first codec from the SDP offer that is also in the list of active codecs. <p> If you disable this option, then the first codec from the active codecs that is also in the SDP offer is picked. Follow codec &preference from far end on outgoing calls Alt+P <p> For outgoing calls, follow the preference from the far-end (SDP answer). Pick the first codec from the SDP answer that is also in the list of active codecs. <p> If you disable this option, then the first codec from the active codecs that is also in the SDP answer is picked. &iLBC iLBC i&LBC payload type: iLBC &payload size (ms): The dynamic type value (96 or higher) to be used for iLBC. 20 30 The preferred payload size for iLBC. &Speex Speex Perceptual &enhancement Alt+E Perceptual enhancement is a part of the decoder which, when turned on, tries to reduce (the perception of) the noise produced by the coding/decoding process. In most cases, perceptual enhancement make the sound further from the original objectively (if you use SNR), but in the end it still sounds better (subjective improvement). &Ultra wide band payload type: Alt+V &Wide band payload type: Alt+B Variable bit-rate (VBR) allows a codec to change its bit-rate dynamically to adapt to the "difficulty" of the audio being encoded. In the example of Speex, sounds like vowels and high-energy transients require a higher bit-rate to achieve good quality, while fricatives (e.g. s,f sounds) can be coded adequately with less bits. For this reason, VBR can achieve a lower bit-rate for the same quality, or a better quality for a certain bit-rate. Despite its advantages, VBR has two main drawbacks: first, by only specifying quality, there's no guarantee about the final average bit-rate. Second, for some real-time applications like voice over IP (VoIP), what counts is the maximum bit-rate, which must be low enough for the communication channel. The dynamic type value (96 or higher) to be used for speex wide band. Co&mplexity: Discontinuous transmission is an addition to VAD/VBR operation, that allows one to stop transmitting completely when the background noise is stationary. The dynamic type value (96 or higher) to be used for speex narrow band. With Speex, it is possible to vary the complexity allowed for the encoder. This is done by controlling how the search is performed with an integer ranging from 1 to 10 in a way that's similar to the -1 to -9 options to gzip and bzip2 compression utilities. For normal use, the noise level at complexity 1 is between 1 and 2 dB higher than at complexity 10, but the CPU requirements for complexity 10 is about 5 times higher than for complexity 1. In practice, the best trade-off is between complexity 2 and 4, though higher settings are often useful when encoding non-speech sounds like DTMF tones. &Narrow band payload type: G.726 G.726 &40 kbps payload type: The dynamic type value (96 or higher) to be used for G.726 40 kbps. The dynamic type value (96 or higher) to be used for G.726 32 kbps. G.726 &24 kbps payload type: The dynamic type value (96 or higher) to be used for G.726 24 kbps. G.726 &32 kbps payload type: The dynamic type value (96 or higher) to be used for G.726 16 kbps. G.726 &16 kbps payload type: Codeword &packing order: RFC 3551 ATM AAL2 There are 2 standards to pack the G.726 codewords into an RTP packet. RFC 3551 is the default packing method. Some SIP devices use ATM AAL2 however. If you experience bad quality using G.726 with RFC 3551 packing, then try ATM AAL2 packing. DT&MF DTMF The dynamic type value (96 or higher) to be used for DTMF events (RFC 2833). DTMF vo&lume: The power level of the DTMF tone in dB. The pause after a DTMF tone. DTMF &duration: DTMF payload &type: DTMF &pause: dB Duration of a DTMF tone. DTMF t&ransport: Auto RFC 2833 Inband Out-of-band (SIP INFO) <h2>RFC 2833</h2> <p>Send DTMF tones as RFC 2833 telephone events.</p> <h2>Inband</h2> <p>Send DTMF inband.</p> <h2>Auto</h2> <p>If the far end of your call supports RFC 2833, then a DTMF tone will be send as RFC 2833 telephone event, otherwise it will be sent inband. </p> <h2>Out-of-band (SIP INFO)</h2> <p> Send DTMF out-of-band via a SIP INFO request. </p> General Protocol options Call &Hold variant: RFC 2543 RFC 3264 Indicates if RFC 2543 (set media IP address in SDP to 0.0.0.0) or RFC 3264 (use direction attributes in SDP) is used to put a call on-hold. Allow m&issing Contact header in 200 OK on REGISTER Alt+I A 200 OK response on a REGISTER request must contain a Contact header. Some registrars however, do not include a Contact header or include a wrong Contact header. This option allows for such a deviation from the specs. &Max-Forwards header is mandatory Alt+M According to RFC 3261 the Max-Forwards header is mandatory. But many implementations do not send this header. If you tick this box, Twinkle will reject a SIP request if Max-Forwards is missing. Put &registration expiry time in contact header Alt+R In a REGISTER message the expiry time for registration can be put in the Contact header or in the Expires header. If you tick this box it will be put in the Contact header, otherwise it goes in the Expires header. &Use compact header names Indicates if compact header names should be used for headers that have a compact form. Allow SDP change during call setup <p>A SIP UAS may send SDP in a 1XX response for early media, e.g. ringing tone. When the call is answered the SIP UAS should send the same SDP in the 200 OK response according to RFC 3261. Once SDP has been received, SDP in subsequent responses should be discarded.</p> <p>By allowing SDP to change during call setup, Twinkle will not discard SDP in subsequent responses and modify the media stream if the SDP is changed. When the SDP in a response is changed, it must have a new version number in the o= line.</p> Use domain &name to create a unique contact header value Alt+N <p> Twinkle creates a unique contact header value by combining the SIP user name and domain: </p> <p> <tt>&nbsp;user_domain@local_ip</tt> </p> <p> This way 2 user profiles, having the same user name but different domain names, have unique contact addresses and hence can be activated simultaneously. </p> <p> Some proxies do not handle a contact header value like this. You can disable this option to get a contact header value like this: </p> <p> <tt>&nbsp;user@local_ip</tt> </p> <p> This format is what most SIP phones use. </p> &Encode Via, Route, Record-Route as list The Via, Route and Record-Route headers can be encoded as a list of comma separated values or as multiple occurrences of the same header. Redirection &Allow redirection Alt+A Indicates if Twinkle should redirect a request if a 3XX response is received. Ask user &permission to redirect Indicates if Twinkle should ask the user before redirecting a request when a 3XX response is received. Max re&directions: The number of redirect addresses that Twinkle tries at a maximum before it gives up redirecting a request. This prevents a request from getting redirected forever. SIP extensions disabled supported required preferred Indicates if the 100rel extension (PRACK) is supported:<br><br> <b>disabled</b>: 100rel extension is disabled <br><br> <b>supported</b>: 100rel is supported (it is added in the supported header of an outgoing INVITE). A far-end can now require a PRACK on a 1xx response. <br><br> <b>required</b>: 100rel is required (it is put in the require header of an outgoing INVITE). If an incoming INVITE indicates that it supports 100rel, then Twinkle will require a PRACK when sending a 1xx response. A call will fail when the far-end does not support 100rel. <br><br> <b>preferred</b>: Similar to required, but if a call fails because the far-end indicates it does not support 100rel (420 response) then the call will be re-attempted without the 100rel requirement. &100 rel (PRACK): Replaces Indicates if the Replaces-extenstion is supported. REFER Call transfer (REFER) Alt+T Indicates if Twinkle should transfer a call if a REFER request is received. As&k user permission to transfer Alt+K Indicates if Twinkle should ask the user before transferring a call when a REFER request is received. Hold call &with referrer while setting up call to transfer target Alt+W Indicates if Twinkle should put the current call on hold when a REFER request to transfer a call is received. Ho&ld call with referee before sending REFER Alt+L Indicates if Twinkle should put the current call on hold when you transfer a call. Auto re&fresh subscription to refer event while call transfer is not finished While a call is being transferred, the referee sends NOTIFY messages to the referrer about the progress of the transfer. These messages are only sent for a short interval which length is determined by the referee. If you tick this box, the referrer will automatically send a SUBSCRIBE to lengthen this interval if it is about to expire and the transfer has not yet been completed. Attended refer to AoR (Address of Record) An attended call transfer should use the contact URI as a refer target. A contact URI may not be globally routable however. Alternatively the AoR (Address of Record) may be used. A disadvantage is that the AoR may route to multiple endpoints in case of forking whereas the contact URI routes to a single endoint. Privacy Privacy options &Send P-Preferred-Identity header when hiding user identity Include a P-Preferred-Identity header with your identity in an INVITE request for a call with identity hiding. SIP transport UDP TCP Transport mode for SIP. In auto mode, the size of a message determines which transport protocol is used. Messages larger than the UDP threshold are sent via TCP. Smaller messages are sent via UDP. T&ransport protocol: UDP t&hreshold: Messages larger than the threshold are sent via TCP. Smaller messages are sent via UDP. NAT traversal &NAT traversal not needed Choose this option when there is no NAT device between you and your SIP proxy or when your SIP provider offers hosted NAT traversal. &Use statically configured public IP address inside SIP messages Indicates if Twinkle should use the public IP address specified in the next field inside SIP message, i.e. in SIP headers and SDP body instead of the IP address of your network interface.<br><br> When you choose this option you have to create static address mappings in your NAT device as well. You have to map the RTP ports on the public IP address to the same ports on the private IP address of your PC. Use &STUN (does not work for incoming TCP) Choose this option when your SIP provider offers a STUN server for NAT traversal. S&TUN server: The hostname, domain name or IP address of the STUN server. &Public IP address: The public IP address of your NAT. Telephone numbers Only &display user part of URI for telephone number If a URI indicates a telephone number, then only display the user part. E.g. if a call comes in from sip:123456@twinklephone.com then display only "123456" to the user. A URI indicates a telephone number if it contains the "user=phone" parameter or when it has a numerical user part and you ticked the next option. &URI with numerical user part is a telephone number If you tick this option, then Twinkle considers a SIP address that has a user part that consists of digits, *, #, + and special symbols only as a telephone number. In an outgoing message, Twinkle will add the "user=phone" parameter to such a URI. &Remove special symbols from numerical dial strings Telephone numbers are often written with special symbols like dashes and brackets to make them readable to humans. When you dial such a number the special symbols must not be dialed. To allow you to simply copy/paste such a number into Twinkle, Twinkle can remove these symbols when you hit the dial button. &Special symbols: The special symbols that may be part of a telephone number for nice formatting, but must be removed when dialing. Number conversion Match expression Replace <p> Often the format of the telphone numbers you need to dial is different from the format of the telephone numbers stored in your address book, e.g. your numbers start with a +-symbol followed by a country code, but your provider expects '00' instead of the '+', or you are at the office and all your numbers need to be prefixed with a '9' to access an outside line. Here you can specify number format conversion using Perl style regular expressions and format strings. </p> <p> For each number you dial, Twinkle will try to find a match in the list of match expressions. For the first match it finds, the number will be replaced with the format string. If no match is found, the number stays unchanged. </p> <p> The number conversion rules are also applied to incoming calls, so the numbers are displayed in the format you want. </p> <h3>Example 1</h3> <p> Assume your country code is 31 and you have stored all numbers in your address book in full international number format, e.g. +318712345678. For dialling numbers in your own country you want to strip of the '+31' and replace it by a '0'. For dialling numbers abroad you just want to replace the '+' by '00'. </p> <p> The following rules will do the trick: </p> <blockquote> <tt> Match expression = \+31([0-9]*) , Replace = 0$1<br> Match expression = \+([0-9]*) , Replace = 00$1</br> </tt> </blockquote> <h3>Example 2</h3> <p> You are at work and all telephone numbers starting with a 0 should be prefixed with a 9 for an outside line. </p> <blockquote> <tt> Match expression = 0[0-9]* , Replace = 9$&<br> </tt> </blockquote> Move the selected number conversion rule upwards in the list. Move the selected number conversion rule downwards in the list. &Add Add a number conversion rule. Re&move Remove the selected number conversion rule. &Edit Edit the selected number conversion rule. Type a telephone number here an press the Test button to see how it is converted by the list of number conversion rules. &Test Test how a number is converted by the number conversion rules. When an incoming call is received, this timer is started. If the user answers the call, the timer is stopped. If the timer expires before the user answers the call, then Twinkle will reject the call with a "480 User Not Responding". NAT &keep alive: &No answer: Select ring back tone file. Select ring tone file. Ring &back tone: <p> Specify the file name of a .wav file that you want to be played as ring back tone for this user. </p> <p> This ring back tone overrides the ring back tone settings in the system settings. </p> <p> Specify the file name of a .wav file that you want to be played as ring tone for this user. </p> <p> This ring tone overrides the ring tone settings in the system settings. </p> &Ring tone: <p> This script is called when you release a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing SIP BYE request are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=local_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the BYE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. Select script file. <p> This script is called when an incoming call fails. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing SIP failure response are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=in_call_failed</b>. <b>SIPSTATUS_CODE</b> contains the status code of the failure response. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> This script is called when the remote party releases a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming SIP BYE request are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=remote_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the BYE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> You can customize the way Twinkle handles incoming calls. Twinkle can call a script when a call comes in. Based on the output of the script Twinkle accepts, rejects or redirects the call. When accepting the call, the ring tone can be customized by the script as well. The script can be any executable program. </p> <p> <b>Note:</b> Twinkle pauses while your script runs. It is recommended that your script does not take more than 200 ms. When you need more time, you can send the parameters followed by <b>end</b> and keep on running. Twinkle will continue when it receives the <b>end</b> parameter. </p> <p> With your script you can customize call handling by outputting one or more of the following parameters to stdout. Each parameter should be on a separate line. </p> <p> <blockquote> <tt> action=[ continue | reject | dnd | redirect | autoanswer ]<br> reason=&lt;string&gt;<br> contact=&lt;address to redirect to&gt;<br> caller_name=&lt;name of caller to display&gt;<br> ringtone=&lt;file name of .wav file&gt;<br> display_msg=&lt;message to show on display&gt;<br> end<br> </tt> </blockquote> </p> <h2>Parameters</h2> <h3>action</h3> <p> <b>continue</b> - continue call handling as usual<br> <b>reject</b> - reject call<br> <b>dnd</b> - deny call with do not disturb indication<br> <b>redirect</b> - redirect call to address specified by <b>contact</b><br> <b>autoanswer</b> - automatically answer a call<br> </p> <p> When the script does not write an action to stdout, then the default action is continue. </p> <p> <b>reason: </b> With the reason parameter you can set the reason string for reject or dnd. This might be shown to the far-end user. </p> <p> <b>caller_name: </b> This parameter will override the display name of the caller. </p> <p> <b>ringtone: </b> The ringtone parameter specifies the .wav file that will be played as ring tone when action is continue. </p> <h2>Environment variables</h2> <p> The values of all SIP headers in the incoming INVITE message are passed in environment variables to your script. The variable names are formatted as <b>SIP_&lt;HEADER_NAME&gt;</b> E.g. SIP_FROM contains the value of the from header. </p> <p> TWINKLE_TRIGGER=in_call. SIPREQUEST_METHOD=INVITE. The request-URI of the INVITE will be passed in <b>SIPREQUEST_URI</b>. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> This script is called when the remote party answers your call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming 200 OK are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> This script is called when you answer an incoming call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing 200 OK are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=in_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. Call released locall&y: <p> This script is called when an outgoing call fails. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming SIP failure response are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call_failed</b>. <b>SIPSTATUS_CODE</b> contains the status code of the failure response. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> This script is called when you make a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing INVITE are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call</b>. <b>SIPREQUEST_METHOD=INVITE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the INVITE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. Outgoing call a&nswered: Incoming call &failed: &Incoming call: Call released &remotely: Incoming call &answered: O&utgoing call: Out&going call failed: &Enable ZRTP/SRTP encryption When ZRTP/SRTP is enabled, then Twinkle will try to encrypt the audio of each call you originate or receive. Encryption will only succeed if the remote party has ZRTP/SRTP support enabled. If the remote party does not support ZRTP/SRTP, then the audio channel will stay unecrypted. ZRTP settings O&nly encrypt audio if remote party indicated ZRTP support in SDP A SIP endpoint supporting ZRTP may indicate ZRTP support during call setup in its signalling. Enabling this option will cause Twinkle only to encrypt calls when the remote party indicates ZRTP support. &Indicate ZRTP support in SDP Twinkle will indicate ZRTP support during call setup in its signalling. &Popup warning when remote party disables encryption during call A remote party of an encrypted call may send a ZRTP go-clear command to stop encryption. When Twinkle receives this command it will popup a warning if this option is enabled. &Voice mail address: The SIP address or telephone number to access your voice mail. Unsollicited Sollicited <H2>Message waiting indication type</H2> <p> If your provider offers the message waiting indication service, then Twinkle can show you when new voice mail messages are waiting. Ask your provider which type of message waiting indication is offered. </p> <H3>Unsollicited</H3> <p> Asterisk provides unsollicited message waiting indication. </p> <H3>Sollicited</H3> <p> Sollicited message waiting indication as specified by RFC 3842. </p> &MWI type: Sollicited MWI Subscription &duration: Mailbox &user name: The hostname, domain name or IP address of your voice mailbox server. For sollicited MWI, an endpoint subscribes to the message status for a limited duration. Just before the duration expires, the endpoint should refresh the subscription. Your user name for accessing your voice mailbox. Mailbox &server: Via outbound &proxy Check this option if Twinkle should send SIP messages to the mailbox server via the outbound proxy. &Maximum number of sessions: When you have this number of instant message sessions open, new incoming message sessions will be rejected. Your presence &Publish availability at startup Publish your availability at startup. Publication &refresh interval (sec): Refresh rate of presence publications. Buddy presence &Subscription refresh interval (sec): Refresh rate of presence subscriptions. Dynamic payload type %1 is used more than once. You must fill in a user name for your SIP account. You must fill in a domain name for your SIP account. This could be the hostname or IP address of your PC if you want direct PC to PC dialing. Invalid domain. Invalid user name. Invalid value for registrar. Invalid value for outbound proxy. You must fill in a mailbox user name. You must fill in a mailbox server Invalid mailbox server. Invalid mailbox user name. Value for public IP address missing. Invalid value for STUN server. Ring tones Description of .wav files in file dialog Choose ring tone Ring back tones Description of .wav files in file dialog All files Choose incoming call script Choose incoming call answered script Choose incoming call failed script Choose outgoing call script Choose outgoing call answered script Choose outgoing call failed script Choose local release script Choose remote release script %1 converts to %2 P&ersistent TCP connection Keep the TCP connection established during registration open such that the SIP proxy can reuse this connection to send incoming requests. Application ping packets are sent to test if the connection is still alive. &Send composing indications when typing a message. Twinkle sends a composing indication when you type a message. This way the recipient can see that you are typing. AKA AM&F: A&KA OP: Authentication management field for AKAv1-MD5 authentication. Operator variant key for AKAv1-MD5 authentication. Prepr&ocessing Preprocessing (improves quality at remote end) &Automatic gain control Automatic gain control (AGC) is a feature that deals with the fact that the recording volume may vary by a large amount between different setups. The AGC provides a way to adjust a signal to a reference volume. This is useful because it removes the need for manual adjustment of the microphone gain. A secondary advantage is that by setting the microphone gain to a conservative (low) level, it is easier to avoid clipping. Automatic gain control &level: Automatic gain control level represents percentual value of automatic gain setting of a microphone. Recommended value is about 25%. &Voice activity detection When enabled, voice activity detection detects whether the input signal represents a speech or a silence/background noise. &Noise reduction The noise reduction can be used to reduce the amount of background noise present in the input signal. This provides higher quality speech. Acoustic &Echo Cancellation In any VoIP communication, if a speech from the remote end is played in the local loudspeaker, then it propagates in the room and is captured by the microphone. If the audio captured from the microphone is sent directly to the remote end, then the remote user hears an echo of his voice. An acoustic echo cancellation is designed to remove the acoustic echo before it is sent to the remote end. It is important to understand that the echo canceller is meant to improve the quality on the remote end. Variable &bit-rate Discontinuous &Transmission &Quality: Speex is a lossy codec, which means that it achives compression at the expense of fidelity of the input speech signal. Unlike some other speech codecs, it is possible to control the tradeoff made between quality and bit-rate. The Speex encoding process is controlled most of the time by a quality parameter that ranges from 0 to 10. bytes Use tel-URI for telephone &number Expand a dialed telephone number to a tel-URI instead of a sip-URI. Accept call &transfer request (incoming REFER) Allow call transfer while consultation in progress When you perform an attended call transfer, you normally transfer the call after you established a consultation call. If you enable this option you can transfer the call while the consultation call is still in progress. This is a non-standard implementation and may not work with all SIP devices. Enable NAT &keep alive Send UDP NAT keep alive packets. If you have enabled STUN or NAT keep alive, then Twinkle will send keep alive packets at this interval rate to keep the address bindings in your NAT device alive. WizardForm Twinkle - Wizard The hostname, domain name or IP address of the STUN server. S&TUN server: The SIP user name given to you by your provider. It is the user part in your SIP address, <b>username</b>@domain.com This could be a telephone number. <br><br> This field is mandatory. &Domain*: Choose your SIP service provider. If your SIP service provider is not in the list, then select <b>Other</b> and fill in the settings you received from your provider.<br><br> If you select one of the predefined SIP service providers then you only have to fill in your name, user name, authentication name and password. &Authentication name: &Your name: Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. The domain part of your SIP address, username@<b>domain.com</b>. Instead of a real domain this could also be the hostname or IP address of your <b>SIP proxy</b>. If you want direct IP phone to IP phone communications then you fill in the hostname or IP address of your computer. <br><br> This field is mandatory. This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. SIP pro&xy: The hostname, domain name or IP address of your SIP proxy. If this is the same value as your domain, you may leave this field empty. &SIP service provider: &Password: &User name*: Your password for authentication. &OK Alt+O &Cancel Alt+C None (direct IP to IP calls) Other User profile wizard: You must fill in a user name for your SIP account. You must fill in a domain name for your SIP account. This could be the hostname or IP address of your PC if you want direct PC to PC dialing. Invalid value for SIP proxy. Invalid value for STUN server. YesNoDialog &Yes &No incoming_call Answer Reject twinkle-1.10.1/src/gui/logviewform.cpp000066400000000000000000000041611277565361200177060ustar00rootroot00000000000000#include "logviewform.h" #include #include #include "audits/memman.h" #include "log.h" /* * Constructs a LogViewForm which is a child of 'parent', with the * name 'name' and widget flags set to 'f' * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ LogViewForm::LogViewForm(QWidget* parent) : QDialog(parent) { setupUi(this); } /* * Destroys the object and frees any allocated resources */ LogViewForm::~LogViewForm() { // no need to delete child widgets, Qt does it all for us } bool LogViewForm::isOnBottom() const { const QScrollBar* vsb = logTextEdit->verticalScrollBar(); return (vsb->value() == vsb->maximum()); } void LogViewForm::scrollToBottom() { QScrollBar* vsb = logTextEdit->verticalScrollBar(); vsb->setValue(vsb->maximum()); logTextEdit->update(); } void LogViewForm::show() { if (isVisible()) { raise(); return; } QString fname = log_file->get_filename().c_str(); logfile = new QFile(fname); MEMMAN_NEW(logfile); logstream = NULL; if (logfile->open(QIODevice::ReadOnly)) { logstream = new QTextStream(logfile); MEMMAN_NEW(logstream); logTextEdit->setPlainText(logstream->readAll()); } log_file->enable_inform_user(true); QDialog::show(); // Couldn't get it to scroll AND show contents(!) without this hack QTimer::singleShot(50, this, SLOT(scrollToBottom())); raise(); } void LogViewForm::closeEvent(QCloseEvent* ev) { log_file->enable_inform_user(false); // logTextEdit->clear(); // causes crashes with Qt5 if (logstream) { MEMMAN_DELETE(logstream); delete logstream; logstream = NULL; } logfile->close(); MEMMAN_DELETE(logfile); delete logfile; logfile = NULL; QDialog::closeEvent(ev); } void LogViewForm::update(bool log_zapped) { if (!isVisible()) return; if (log_zapped) { close(); show(); return; } if (logstream) { QString s = logstream->readAll(); if (!s.isNull() && !s.isEmpty()) { bool bottom = isOnBottom(); logTextEdit->appendPlainText(s); if (bottom) scrollToBottom(); } } } void LogViewForm::clear() { logTextEdit->clear(); } twinkle-1.10.1/src/gui/logviewform.h000066400000000000000000000010641277565361200173520ustar00rootroot00000000000000#ifndef LOGVIEWFORM_H #define LOGVIEWFORM_H #include #include #include #include "ui_logviewform.h" class LogViewForm : public QDialog, protected Ui::LogViewForm { Q_OBJECT private: QFile* logfile; QTextStream* logstream; bool isOnBottom() const; public slots: void scrollToBottom(); public: LogViewForm(QWidget* parent = 0); ~LogViewForm(); public slots: void show(); void closeEvent(QCloseEvent* ev); void update(bool log_zapped); void clear(); }; #endif // LOGVIEWFORM_H twinkle-1.10.1/src/gui/logviewform.ui000066400000000000000000000054621277565361200175460ustar00rootroot00000000000000 LogViewForm 0 0 599 472 Twinkle - Log Contents of the current log file (~/.twinkle/twinkle.log) QPlainTextEdit::NoWrap true &Close Alt+C Qt::Horizontal QSizePolicy::Expanding 360 20 Clear the log window. This does <b>not</b> clear the log file itself. C&lear Alt+L logTextEdit clearPushButton closePushButton qfile.h closePushButton clicked() LogViewForm close() 20 20 20 20 clearPushButton clicked() LogViewForm clear() 20 20 20 20 twinkle-1.10.1/src/gui/main.cpp000066400000000000000000001104731277565361200162760ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "twinkle_config.h" #include #include #ifdef HAVE_KDE #include #include #endif #include #include #include #include #include #include "mphoneform.h" #include #include #include #include #include #include #include #include "address_book.h" #include "address_finder.h" #include "call_history.h" #include "cmd_socket.h" #include "events.h" #include "listener.h" #include "log.h" #include "protocol.h" #include "sender.h" #include "transaction_mgr.h" #include "twinkleapplication.h" #include "user.h" #include "util.h" #include "phone.h" #include "gui.h" #include "qt_translator.h" #include "command_args.h" #include "sockets/connection_table.h" #include "sockets/interfaces.h" #include "sockets/socket.h" #include "threads/thread.h" #include "utils/mime_database.h" #include "audits/memman.h" #include using namespace std; using namespace utils; // Class to initialize the random generator before objects of // other classes are created. Initializing just from the main function // is too late. class t_init_rand { public: t_init_rand(); }; t_init_rand::t_init_rand() { srand(time(NULL)); } // Initialize random generator t_init_rand init_rand; // Language translator for the core of Twinkle t_translator *translator = NULL; // Indicates if application is ending (because user pressed Quit) bool end_app; // Memory manager for memory leak tracing t_memman *memman; // IP address on which the phone is running string user_host; // Local host name string local_hostname; // SIP UDP socket for sending and receiving signaling t_socket_udp *sip_socket; // SIP TCP socket for sending and receiving signaling t_socket_tcp *sip_socket_tcp; // SIP connection table for connection oriented transport t_connection_table *connection_table; // Event queue that is handled by the transaction manager thread // The following threads write to this queue // - UDP listener // - transaction layer // - timekeeper t_event_queue *evq_trans_mgr; // Event queue that is handled by the UDP sender thread // The following threads write to this queue: // - phone UAS // - phone UAC // - transaction manager t_event_queue *evq_sender; // Event queue that is handled by the transaction layer thread // The following threads write to this queue // - transaction manager // - timekeeper t_event_queue *evq_trans_layer; // Event queue that is handled by the phone timekeeper thread // The following threads write into this queue // - phone UAS // - phone UAC // - transaction manager t_event_queue *evq_timekeeper; // The timekeeper t_timekeeper *timekeeper; // The transaction manager t_transaction_mgr *transaction_mgr; // The phone t_phone *phone; // User interface t_userintf *ui; // Log file t_log *log_file; // System config t_sys_settings *sys_config; // Call history t_call_history *call_history; // Local address book t_address_book *ab_local; // Mime database t_mime_database *mime_database; /** Command arguments. */ t_command_args g_cmd_args; // Thread id of main thread pthread_t thread_id_main; // Indicates if LinuxThreads or NPTL is active. bool threading_is_LinuxThreads; QSettings* g_gui_state; /** * Parse arguments passed to application * @param argc [in] Number of arguments * @param argv [in] Array of arguments * @param cli_mode [out] Indicates if Twinkle must run in CLI mode. * @param override_lock_file [out] Indicates if an existing lock file must be overriden. * @param config_files [out] User profiles passed on the command line. * @param remain_argc [out] The number of arguments not parsed by this function. * @param remain_argv [out] The arguments not parsed by this function. * remain_argv[0] == argv[0] */ void parse_main_args(int argc, char **argv, bool &cli_mode, bool &override_lock_file, list &config_files, int &remain_argc, char **&remain_argv) { cli_mode = false; override_lock_file = false; config_files.clear(); // Initialize the remaining arguments with the first argument (application name) // from the original arguments. remain_argv = (char**)malloc(argc * sizeof(char*)); remain_argv[0] = argv[0]; remain_argc = 1; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { // Help cout << "Usage: twinkle [options]\n\n"; cout << "Options:\n"; cout << " -c"; cout << "\t\tRun in command line interface (CLI) mode\n"; cout << endl; cout << " --share

"; cout << "\tSet the share directory.\n"; cout << endl; cout << " -f "; cout << "\tStartup with a specific profile. You will not be requested\n"; cout << "\t\tto choose a profile at startup. The profiles that you created\n"; cout << "\t\tare the .cfg files in your .twinkle directory.\n"; cout << "\t\tYou may specify multiple profiles separated by spaces.\n"; cout << endl; cout << " --force"; cout << "\tIf a lock file is detected at startup, then override it\n"; cout << "\t\tand startup.\n"; cout << endl; #if 0 // DEPRECATED cout << " -i "; cout << "\tIf you have multiple IP addresses on your computer,\n"; cout << "\t\tthen you can supply the IP address to use here.\n"; cout << endl; #endif cout << " --sip-port \n"; cout << "\t\tPort for SIP signalling.\n"; cout << "\t\tThis port overrides the port from the system settings.\n"; cout << endl; cout << " --rtp-port \n"; cout << "\t\tPort for RTP.\n"; cout << "\t\tThis port overrides the port from the system settings.\n"; cout << endl; #if 0 // DEPRECATED cout << " --nic "; cout << "\tIf you have multiple NICs on your computer,\n"; cout << "\t\tthen you can supply the NIC name to use here (e.g. eth0).\n"; cout << endl; #endif cout << " --call
\n"; cout << "\t\tInstruct Twinkle to call the address.\n"; cout << "\t\tWhen Twinkle is already running, this will instruct the running\n"; cout << "\t\tprocess to call the address.\n"; cout << "\t\tThe address may be a full or partial SIP URI. A partial SIP URI\n"; cout << "\t\twill be completed with the information from the user profile.\n"; cout << endl; cout << "\t\tA subject may be passed by appending '?subject='\n"; cout << "\t\tto the address.\n"; cout << endl; cout << "\t\tExamples:\n"; cout << "\t\ttwinkle --call 123456\n"; cout << "\t\ttwinkle --call sip:example@example.com?subject=hello\n"; cout << endl; cout << " --cmd \n"; cout << "\t\tInstruct Twinkle to execute the CLI command. You can run\n"; cout << "\t\tall commands from the command line interface mode.\n"; cout << "\t\tWhen Twinkle is already running, this will instruct the running\n"; cout << "\t\tprocess to execute the CLI command.\n"; cout << endl; cout << "\t\tExamples:\n"; cout << "\t\ttwinkle --cmd answer\n"; cout << "\t\ttwinkle --cmd mute\n"; cout << "\t\ttwinkle --cmd 'transfer 12345'\n"; cout << endl; cout << " --immediate"; cout << "\tThis option can be used in conjunction with --call or --cmd\n"; cout << "\t\tIt indicates the the command or call is to be performed\n"; cout << "\t\timmediately without asking the user for any confirmation.\n"; cout << endl; cout << " --set-profile \n"; cout << "\t\tMake the active profile.\n"; cout << "\t\tWhen using this option in conjunction with --call and --cmd,\n"; cout << "\t\tthen the profile is activated before executing --call or \n"; cout << "\t\t--cmd.\n"; cout << endl; cout << " --show"; cout << "\t\tInstruct a running instance of Twinkle to show the main window\n"; cout << "\t\tand take focus.\n"; cout << endl; cout << " --hide"; cout << "\t\tInstruct a running instance of Twinkle to hide in the system tray.\n"; cout << "\t\tIf no system tray is used, then Twinkle will minimize.\n"; cout << endl; cout << " --help-cli [cli command]\n"; cout << "\t\tWithout a cli command this option lists all available CLI\n"; cout << "\t\tcommands. With a CLI command this option prints help on\n"; cout << "\t\tthe CLI command.\n"; cout << endl; cout << " --version"; cout << "\tGet version information.\n"; exit(0); } else if (strcmp(argv[i], "--version") == 0) { // Get version QString s = sys_config->about(false).c_str(); cout << s.toStdString(); exit(0); } else if (strcmp(argv[i], "-c") == 0) { // CLI mode cli_mode = true; } else if (strcmp(argv[i], "--share") == 0) { if (i < argc - 1 && argv[i+1][0] != '-') { i++; sys_config->set_dir_share(argv[i]); } else { cout << argv[0] << ": "; cout << "Directory missing for option '-share'.\n"; exit(0); } } else if (strcmp(argv[i], "-f") == 0) { if (i < argc - 1 && argv[i+1][0] != '-') { while (i < argc -1 && argv[i+1][0] != '-') { i++; // Config file name QString config_file = argv[i]; if (!config_file.endsWith(USER_FILE_EXT)) { config_file += USER_FILE_EXT; } config_files.push_back(config_file.toStdString()); } } else { cout << argv[0] << ": "; cout << "Config file name missing for option '-f'.\n"; exit(0); } } else if (strcmp(argv[i], "--force") == 0) { override_lock_file = true; #if 0 // DEPRECATED } else if (strcmp(argv[i], "-i") == 0) { if (i < argc - 1) { i++; // IP address user_host = argv[i]; if (!exists_interface(user_host)) { cout << argv[0] << ": "; cout << "There is no interface with IP address "; cout << user_host << endl; exit(0); } } else { cout << argv[0] << ": "; cout << "IP address missing for option '-i'.\n"; exit(0); } #endif } else if (strcmp(argv[i], "--sip-port") == 0) { if (i < argc - 1) { i++; g_cmd_args.override_sip_port = atoi(argv[i]); } else { cout << argv[0] << ": "; cout << "Port missing for option '--sip-port'\n"; } } else if (strcmp(argv[i], "--rtp-port") == 0) { if (i < argc - 1) { i++; g_cmd_args.override_rtp_port = atoi(argv[i]); } else { cout << argv[0] << ": "; cout << "Port missing for option '--rtp-port'\n"; } } else if (strcmp(argv[i], "--call") == 0) { if (i < argc - 1) { i++; // SIP URI g_cmd_args.callto_destination = argv[i]; if (g_cmd_args.callto_destination.isEmpty()) { cout << argv[0] << ": "; cout << "--call argument may not be empty.\n"; exit(0); } } else { cout << argv[0] << ": "; cout << "SIP URI missing for option '--call'.\n"; exit(0); } } else if (strcmp(argv[i], "--cmd") == 0) { if (i < argc - 1) { i++; // CLI command g_cmd_args.cli_command = argv[i]; if (g_cmd_args.cli_command.isEmpty()) { cout << argv[0] << ": "; cout << "--cmd argument may not be empty.\n"; exit(0); } } else { cout << argv[0] << ": "; cout << "CLI command missing for option '--cmd'.\n"; exit(0); } } else if (strcmp(argv[i], "--immediate") == 0) { // Immediate mode g_cmd_args.cmd_immediate_mode = true; } else if (strcmp(argv[i], "--set-profile") == 0) { if (i < argc - 1) { i++; // Set profile g_cmd_args.cmd_set_profile = argv[i]; } else { cout << argv[0] << ": "; cout << "Profile missing for option '--set-profile'.\n"; exit(0); } } else if (strcmp(argv[i], "--show") == 0) { // Show main window g_cmd_args.cmd_show = true; } else if (strcmp(argv[i], "--hide") == 0) { // Hide main window g_cmd_args.cmd_hide = true; } else if (strcmp(argv[i], "--help-cli") == 0) { string cmd_help("help "); if (i < argc -1) { i++; // Help CLI cmd_help += argv[i]; } t_phone p; t_userintf u(&p); u.exec_command(cmd_help); exit(0); } else { // Unknown argument. Assume that it is an Qt/KDE argument. remain_argv[remain_argc++] = argv[i]; } } if (!g_cmd_args.callto_destination.isEmpty() && !g_cmd_args.cli_command.isEmpty()) { cout << argv[0] << ": "; cout << "--call and --cmd cannot be used at the same time.\n"; exit(0); } return; } bool open_sip_socket(bool cli_mode) { QString sock_type; // Open socket for SIP signaling try { sock_type = "UDP"; sip_socket = new t_socket_udp(sys_config->get_sip_port(true)); MEMMAN_NEW(sip_socket); if (sip_socket->enable_icmp()) { log_file->write_report("ICMP processing enabled.", "::main"); } else { log_file->write_report("ICMP processing disabled.", "::main"); } sock_type = "TCP"; sip_socket_tcp = new t_socket_tcp(sys_config->get_sip_port()); MEMMAN_NEW(sip_socket_tcp); } catch (int err) { string msg; if (cli_mode) { msg = QString("Failed to create a %1 socket (SIP) on port %2") .arg(sock_type) .arg(sys_config->get_sip_port()).toStdString(); } else { msg = qApp->translate("GUI", "Failed to create a %1 socket (SIP) on port %2") .arg(sock_type) .arg(sys_config->get_sip_port()).toStdString(); } msg += "\n"; msg += get_error_str(err); log_file->write_report(msg, "::main", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); return false; } return true; } QApplication *create_user_interface(bool cli_mode, int argc, char **argv, QTranslator *appTranslator, QTranslator *qtTranslator) { QApplication *qa = NULL; if (cli_mode) { // CLI mode ui = new t_userintf(phone); MEMMAN_NEW(ui); } else { // GUI mode #ifdef HAVE_KDE // Store the defualt mime source factory for the embedded icons. // This is created by Qt. The KApplication constructor seems to destroy // this default. Q3MimeSourceFactory *factory_qt = Q3MimeSourceFactory::takeDefaultFactory(); // Initialize the KApplication KCmdLineArgs::init(argc, argv, "twinkle", PRODUCT_NAME, "Soft phone", PRODUCT_VERSION, true); qa = new t_twinkle_application(); MEMMAN_NEW(qa); // Store the KDE mime source factory Q3MimeSourceFactory *factory_kde = Q3MimeSourceFactory::takeDefaultFactory(); // Make the Qt factory the default to make the embedded icons work. Q3MimeSourceFactory::setDefaultFactory(factory_qt); // Add the KDE factory Q3MimeSourceFactory::addFactory(factory_kde); #else static int tmp = argc; qa = new t_twinkle_application(tmp, argv); MEMMAN_NEW(qa); #endif #if QT_VERSION < 0x050000 // In Qt5, these functions are removed. UTF-8 is the default. QTextCodec::setCodecForCStrings(QTextCodec::codecForName("utf8")); QTextCodec::setCodecForTr(QTextCodec::codecForName("utf8")); #endif g_gui_state = new QSettings(QDir::home().absoluteFilePath(QString("%1/%2").arg(DIR_USER).arg("gui_state.ini")), QSettings::IniFormat, qa); // Install Qt translator // Do not report to memman as the translator will be deleted // automatically when the QApplication is deleted. appTranslator = new QTranslator(0); qtTranslator = new QTranslator(0); QString langName = QLocale::system().name().left(2); qDebug() << "Language name:" << langName; appTranslator->load(QString("twinkle_") + langName, QString(sys_config->get_dir_lang().c_str())); qa->installTranslator(appTranslator); qtTranslator->load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); qa->installTranslator(qtTranslator); qa->setQuitOnLastWindowClosed(false); // Create translator for translation of strings from the core translator = new t_qt_translator(qa); MEMMAN_NEW(translator); ui = new t_gui(phone); MEMMAN_NEW(ui); } return qa; } void blockSignals() { // Dedicated thread will catch SIGALRM, SIGINT, SIGTERM, SIGCHLD signals, // therefore all threads must block these signals. Block now, then all // created threads will inherit the signal mask. // In LinuxThreads the sigwait does not work very well, so // in LinuxThreads a signal handler is used instead. if (!threading_is_LinuxThreads) { sigset_t sigset; sigemptyset(&sigset); sigaddset(&sigset, SIGALRM); sigaddset(&sigset, SIGINT); sigaddset(&sigset, SIGTERM); sigaddset(&sigset, SIGCHLD); sigprocmask(SIG_BLOCK, &sigset, NULL); } else { if (!phone->set_sighandler()) { string msg = "Failed to register signal handler."; log_file->write_report(msg, "::main", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); sys_config->delete_lock_file(); exit(1); } } // Ignore SIGPIPE so read from broken sockets will not cause // the process to terminate. (void)signal(SIGPIPE, SIG_IGN); } int main( int argc, char ** argv ) { string error_msg; bool cli_mode; bool override_lock_file; list config_files; // Initialize globals end_app = false; // Determine threading implementation threading_is_LinuxThreads = t_thread::is_LinuxThreads(); blockSignals(); QApplication *qa = NULL; QTranslator *appTranslator = NULL; QTranslator *qtTranslator = NULL; // Set phone role qputenv("PULSE_PROP_media.role", "phone"); // Store id of main thread thread_id_main = t_thread::self(); memman = new t_memman(); MEMMAN_NEW(memman); connection_table = new t_connection_table(); MEMMAN_NEW(connection_table); evq_trans_mgr = new t_event_queue(); MEMMAN_NEW(evq_trans_mgr); evq_sender = new t_event_queue(); MEMMAN_NEW(evq_sender); evq_trans_layer = new t_event_queue(); MEMMAN_NEW(evq_trans_layer); evq_timekeeper = new t_event_queue(); MEMMAN_NEW(evq_timekeeper); timekeeper = new t_timekeeper(); MEMMAN_NEW(timekeeper); transaction_mgr = new t_transaction_mgr(); MEMMAN_NEW(transaction_mgr); phone = new t_phone(); MEMMAN_NEW(phone); // Create system configuration object sys_config = new t_sys_settings(); MEMMAN_NEW(sys_config); // Parse command line arguments int remain_argc = 0; char **remain_argv = NULL; parse_main_args(argc, argv, cli_mode, override_lock_file, config_files, remain_argc, remain_argv); sys_config->set_override_sip_port(g_cmd_args.override_sip_port); sys_config->set_override_rtp_port(g_cmd_args.override_rtp_port); // Checking the environment and creating the lock is done at // this early stage to improve performance of the --call parameter. // Creation of the QApplication object for the GUI is slow. // However for errors, the user interface must be created to give // either a message box or text formatted error. // Check requirements on environment // If check fails, then display error after user interface has been // created. string env_error_msg; bool env_check_ok = sys_config->check_environment(env_error_msg); // Create a lock file to guarantee that the application runs only once. bool already_running; bool lock_created = false; string lock_error_msg; if (env_check_ok && !(lock_created = sys_config->create_lock_file(false, lock_error_msg, already_running))) { bool must_exit = false; // Show the main window of the running Twinkle process. if (already_running && g_cmd_args.cmd_show) { cmdsocket::cmd_show(); must_exit = true; } // Hide the main window of the running Twinkle process. if (already_running && g_cmd_args.cmd_hide) { cmdsocket::cmd_hide(); must_exit = true; } // Activate a profile in the running Twinkle process. if (already_running && !g_cmd_args.cmd_set_profile.isEmpty()) { cmdsocket::cmd_cli(string("user ") + g_cmd_args.cmd_set_profile.toStdString(), true); // Do not exit now as this option may be used in conjunction // with --call or --cmd must_exit = true; } // If Twinkle is running already and the --call parameter // is present, then send the call destination to the running // Twinkle process. if (already_running && !g_cmd_args.callto_destination.isEmpty()) { cmdsocket::cmd_call(g_cmd_args.callto_destination.toStdString(), g_cmd_args.cmd_immediate_mode); exit(0); } // If the --cmd parameter is present, send the cli command // to the running Twinkle process if (already_running && !g_cmd_args.cli_command.isEmpty()) { cmdsocket::cmd_cli(g_cmd_args.cli_command.toStdString(), g_cmd_args.cmd_immediate_mode); exit(0); } // Exit if an instruction for a running instance was given. if (must_exit) { exit(0); } } // Read system configuration bool sys_config_read = sys_config->read_config(error_msg); qa = create_user_interface(cli_mode, remain_argc, remain_argv, appTranslator, qtTranslator); if (!sys_config_read) { ui->cb_show_msg(error_msg, MSG_CRITICAL); exit(1); } user_host = AUTO_IP4_ADDRESS; local_hostname = get_local_hostname(); if (!env_check_ok) { // Environment is not good // Call the check_environment once more to get proper translation // of the error message. The previous check was done before // the QApplication was created. (void)sys_config->check_environment(env_error_msg); ui->cb_show_msg(env_error_msg, MSG_CRITICAL); exit(1); } // Show error if lock file could not be created if (!lock_created) { string msg; // Call create lock file once more to get proper translation of // error message. if (!sys_config->create_lock_file(false, msg, already_running)) { if (already_running) { if (!cli_mode) { msg += "\n\n"; msg += qApp->translate("GUI", "Override lock file and start anyway?").toStdString(); } if (override_lock_file || ui->cb_ask_msg(msg, MSG_WARNING)) { sys_config->delete_lock_file(); if (!sys_config->create_lock_file(true, msg, already_running)) { ui->cb_show_msg(msg, MSG_CRITICAL); exit(1); } } else { exit(1); } } else { ui->cb_show_msg(msg, MSG_CRITICAL); exit(1); } } // If for some obscure reason the lock file could be // created this time, then continue. } // Create log file log_file = new t_log(); MEMMAN_NEW(log_file); // Write threading implementation to log file. May be useful for debugging. if (threading_is_LinuxThreads) { log_file->write_report("Threading implementation is LinuxThreads.", "::main", LOG_NORMAL, LOG_INFO); } else { log_file->write_report("Threading implementation is NPTL.", "::main", LOG_NORMAL, LOG_INFO); } // Check if the previous Twinkle session was stopped by a system // shutdow and now gets restored. if (qa && qa->isSessionRestored()) { QString msg = "Restore session: " + qa->sessionId(); log_file->write_report(msg.toStdString(), "::main"); if (sys_config->get_ui_session_id() == qa->sessionId().toStdString()) { config_files = sys_config->get_ui_session_active_profiles(); // Note: the GUI state is restore in t_gui::run() } else { log_file->write_header("::main", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Cannot restore session.\n"); log_file->write_raw("Stored session id: "); log_file->write_raw(sys_config->get_ui_session_id()); log_file->write_endl(); log_file->write_footer(); } } // Get default values from system configuration if (config_files.empty()) { list start_user_profiles = sys_config->get_start_user_profiles(); for (list::iterator i = start_user_profiles.begin(); i != start_user_profiles.end(); i++) { QString config_file = (*i).c_str(); config_file += USER_FILE_EXT; config_files.push_back(config_file.toStdString()); } } bool profile_selected = false; while(!profile_selected) { // Select user profile if (config_files.empty()) { if (!ui->select_user_config(config_files)) { sys_config->delete_lock_file(); exit(1); } } for (list::iterator i = config_files.begin(); i != config_files.end(); i++) { t_user user_config; // Read user configuration if (user_config.read_config(*i, error_msg)) { t_user *dup_user; if (phone->add_phone_user( user_config, &dup_user)) { profile_selected = true; } else { if (cli_mode) { error_msg = QString("The following profiles are both for user %1").arg(user_config.get_name().c_str()).toStdString(); } else { error_msg = qApp->translate("GUI", "The following profiles are both for user %1").arg(user_config.get_name().c_str()).toStdString(); } error_msg += ":\n\n"; error_msg += user_config.get_profile_name(); error_msg += "\n"; error_msg += dup_user->get_profile_name(); error_msg += "\n\n"; if (cli_mode) { error_msg += QString("You can only run multiple profiles for different users.").toStdString(); error_msg += "\n"; error_msg += QString("If these are users for different domains, then enable the following option in your user profile (SIP protocol):").toStdString(); error_msg += "\n"; error_msg += QString("Use domain name to create a unique contact header").toStdString(); } else { error_msg += qApp->translate("GUI", "You can only run multiple profiles for different users.").toStdString(); error_msg += "\n"; error_msg += qApp->translate("GUI", "If these are users for different domains, then enable the following option in your user profile (SIP protocol)").toStdString(); error_msg += ":\n"; error_msg += qApp->translate("GUI", "Use domain name to create a unique contact header").toStdString(); } ui->cb_show_msg(error_msg, MSG_CRITICAL); profile_selected = false; break; } } else { ui->cb_show_msg(error_msg, MSG_CRITICAL); profile_selected = false; break; } } if (profile_selected && !open_sip_socket(cli_mode)) { // Opening SIP socket failed. Let user pick a user profile // again, so he can make changes in settings to fix the error. profile_selected = false; } // In CLI mode the user cannot select another profile. if (!profile_selected) { if (cli_mode) { sys_config->delete_lock_file(); exit(1); } } config_files.clear(); } // Initialize RTP port settings. phone->init_rtp_ports(); // Create call history call_history = new t_call_history(); MEMMAN_NEW(call_history); // Read call history if (!call_history->load(error_msg)) { log_file->write_report(error_msg, "::main", LOG_NORMAL, LOG_WARNING); } // Create local address book ab_local = new t_address_book(); MEMMAN_NEW(ab_local); // Read local address book if (!ab_local->load(error_msg)) { log_file->write_report(error_msg, "::main", LOG_NORMAL, LOG_WARNING); ui->cb_show_msg(error_msg, MSG_WARNING); } // Preload the address finder (KABC is only available in GUI mode) if (!cli_mode) { t_address_finder::preload(); } // Create mime database mime_database = new t_mime_database(); MEMMAN_NEW(mime_database); if (!mime_database->load(error_msg)) { log_file->write_report(error_msg, "::main", LOG_NORMAL, LOG_WARNING); } // Discover NAT type if STUN is enabled list user_list = phone->ref_users(); ui->cb_nat_discovery_progress_start(user_list.size()); list msg_list; int progressStep = 0; for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { ui->cb_nat_discovery_progress_step(progressStep); if (ui->cb_nat_discovery_cancelled()) { log_file->write_report("User aborted NAT discovery.", "::main"); sys_config->delete_lock_file(); exit(1); } if (!phone->stun_discover_nat(*i, error_msg)) { msg_list.push_back(error_msg); } progressStep++; } ui->cb_nat_discovery_finished(); for (list::iterator i = msg_list.begin(); i != msg_list.end(); i++) { ui->cb_show_msg(*i, MSG_WARNING); } // Open socket for external commands from the command line string cmd_sock_name = sys_config->get_dir_user(); cmd_sock_name += '/'; cmd_sock_name += CMD_SOCKNAME; t_socket_local *sock_cmd = NULL; try { // The local socket may still exist if Twinkle got killed // previously, so remove it if it is still there. unlink(cmd_sock_name.c_str()); sock_cmd = new t_socket_local(); MEMMAN_NEW(sock_cmd); sock_cmd->bind(cmd_sock_name); sock_cmd->listen(5); string log_msg = "Created local socket: "; log_msg += cmd_sock_name; log_file->write_report(log_msg, "::main"); } catch (int e) { if (sock_cmd) { MEMMAN_DELETE(sock_cmd); delete sock_cmd; sock_cmd = NULL; } string log_msg = "Failed to create local socket: "; log_msg += cmd_sock_name; log_msg += "\n"; log_msg += get_error_str(e); log_msg += "\n"; log_file->write_report(log_msg, "::main", LOG_NORMAL, LOG_WARNING); } // Create threads t_thread *thr_sender; t_thread *thr_tcp_sender; t_thread *thr_listen_udp; t_thread *thr_listen_data_tcp; t_thread *thr_listen_conn_tcp; t_thread *thr_conn_timeout_handler; t_thread *thr_timekeeper; t_thread *thr_alarm_catcher = NULL; t_thread *thr_sig_catcher = NULL; t_thread *thr_trans_mgr; t_thread *thr_phone_uas; t_thread *thr_listen_cmd = NULL; try { // SIP sender thread thr_sender = new t_thread(sender_loop, NULL); MEMMAN_NEW(thr_sender); // SIP TCP sender thread thr_tcp_sender = new t_thread(tcp_sender_loop, NULL); MEMMAN_NEW(thr_tcp_sender); // UDP listener thread thr_listen_udp = new t_thread(listen_udp, NULL); MEMMAN_NEW(thr_listen_udp); // TCP data listener thread thr_listen_data_tcp = new t_thread(listen_for_data_tcp, NULL); MEMMAN_NEW(thr_listen_data_tcp); // TCP connection listener thread thr_listen_conn_tcp = new t_thread(listen_for_conn_requests_tcp, NULL); MEMMAN_NEW(thr_listen_conn_tcp); // Connection timeout handler thread thr_conn_timeout_handler = new t_thread(connection_timeout_main, NULL); MEMMAN_NEW(thr_conn_timeout_handler); // Timekeeper thread thr_timekeeper = new t_thread(timekeeper_main, NULL); MEMMAN_NEW(thr_timekeeper); if (!threading_is_LinuxThreads) { // Alarm catcher thread thr_alarm_catcher = new t_thread(timekeeper_sigwait, NULL); MEMMAN_NEW(thr_alarm_catcher); // Signal catcher thread thr_sig_catcher = new t_thread(phone_sigwait, NULL); MEMMAN_NEW(thr_sig_catcher); } // Transaction manager thread thr_trans_mgr = new t_thread(transaction_mgr_main, NULL); MEMMAN_NEW(thr_trans_mgr); // Phone thread (UAS) thr_phone_uas = new t_thread(phone_uas_main, NULL); MEMMAN_NEW(thr_phone_uas); // External command listener thread if (sock_cmd) { thr_listen_cmd = new t_thread(cmdsocket::listen_cmd, sock_cmd); MEMMAN_NEW(thr_listen_cmd); } } catch (int) { string msg = "Failed to create threads."; log_file->write_report(msg, "::main", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); sys_config->delete_lock_file(); exit(1); } // Validate sound devices if (!sys_config->exec_audio_validation(true, true, true, error_msg)) { ui->cb_show_msg(error_msg, MSG_WARNING); } // Start UI event loop (CLI/QApplication/KApplication) try { ui->run(); } catch (string e) { string msg = "Exception: "; msg += e; log_file->write_report(msg, "::main", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); sys_config->delete_lock_file(); exit(1); } catch (int e) { string msg = "Error code exception: "; msg += e; log_file->write_report(msg, "::main", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); sys_config->delete_lock_file(); exit(1); } catch (const std::exception& e) { string msg = "std::exception exception: "; msg += e.what(); msg += " ("; msg += typeid(e).name(); msg += ")"; log_file->write_report(msg, "::main", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); sys_config->delete_lock_file(); exit(1); } catch (...) { string msg = "Unknown exception"; log_file->write_report(msg, "::main", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); sys_config->delete_lock_file(); exit(1); } // Application is ending end_app = true; // Terminate threads // Kill the threads getting receiving input from the outside world first, // so no new inputs come in during termination. if (thr_listen_cmd) { thr_listen_cmd->cancel(); thr_listen_cmd->join(); log_file->write_report("thr_listen_cmd stopped.", "::main", LOG_NORMAL, LOG_DEBUG); } thr_listen_udp->cancel(); thr_listen_udp->join(); log_file->write_report("thr_listen_udp stopped.", "::main", LOG_NORMAL, LOG_DEBUG); thr_listen_conn_tcp->cancel(); thr_listen_conn_tcp->join(); log_file->write_report("thr_listen_conn_tcp stopped.", "::main", LOG_NORMAL, LOG_DEBUG); connection_table->cancel_select(); thr_listen_data_tcp->join(); log_file->write_report("thr_listen_data_tcp stopped.", "::main", LOG_NORMAL, LOG_DEBUG); thr_conn_timeout_handler->join(); log_file->write_report("thr_conn_timeout_handler stopped.", "::main", LOG_NORMAL, LOG_DEBUG); thr_tcp_sender->join(); log_file->write_report("thr_tcp_sender stopped.", "::main", LOG_NORMAL, LOG_DEBUG); evq_trans_layer->push_quit(); thr_phone_uas->join(); log_file->write_report("thr_phone_uas stopped.", "::main", LOG_NORMAL, LOG_DEBUG); evq_trans_mgr->push_quit(); thr_trans_mgr->join(); if (!threading_is_LinuxThreads) { try { thr_sig_catcher->cancel(); } catch (int) { // Thread terminated already by itself } thr_sig_catcher->join(); log_file->write_report("thr_sig_catcher stopped.", "::main", LOG_NORMAL, LOG_DEBUG); thr_alarm_catcher->cancel(); thr_alarm_catcher->join(); log_file->write_report("thr_alarm_catcher stopped.", "::main", LOG_NORMAL, LOG_DEBUG); } evq_timekeeper->push_quit(); thr_timekeeper->join(); log_file->write_report("thr_timekeeper stopped.", "::main", LOG_NORMAL, LOG_DEBUG); evq_sender->push_quit(); thr_sender->join(); log_file->write_report("thr_sender stopped.", "::main", LOG_NORMAL, LOG_DEBUG); sys_config->remove_all_tmp_files(); if (thr_listen_cmd) { MEMMAN_DELETE(thr_listen_cmd); delete thr_listen_cmd; } MEMMAN_DELETE(thr_phone_uas); delete thr_phone_uas; MEMMAN_DELETE(thr_trans_mgr); delete thr_trans_mgr; MEMMAN_DELETE(thr_timekeeper); delete thr_timekeeper; MEMMAN_DELETE(thr_conn_timeout_handler); delete thr_conn_timeout_handler; if (!threading_is_LinuxThreads) { MEMMAN_DELETE(thr_sig_catcher); delete thr_sig_catcher; MEMMAN_DELETE(thr_alarm_catcher); delete thr_alarm_catcher; } MEMMAN_DELETE(thr_listen_udp); delete thr_listen_udp; MEMMAN_DELETE(thr_sender); delete thr_sender; MEMMAN_DELETE(thr_tcp_sender); delete thr_tcp_sender; MEMMAN_DELETE(thr_listen_data_tcp); delete thr_listen_data_tcp; MEMMAN_DELETE(thr_listen_conn_tcp); delete thr_listen_conn_tcp; MEMMAN_DELETE(mime_database); delete mime_database; MEMMAN_DELETE(ab_local); delete ab_local; MEMMAN_DELETE(call_history); delete call_history; call_history = NULL; MEMMAN_DELETE(ui); delete ui; ui = NULL; MEMMAN_DELETE(connection_table); delete connection_table; MEMMAN_DELETE(sip_socket_tcp); delete sip_socket_tcp; MEMMAN_DELETE(sip_socket); delete sip_socket; if (sock_cmd) { MEMMAN_DELETE(sock_cmd); delete sock_cmd; unlink(cmd_sock_name.c_str()); } MEMMAN_DELETE(phone); delete phone; MEMMAN_DELETE(transaction_mgr); delete transaction_mgr; MEMMAN_DELETE(timekeeper); delete timekeeper; MEMMAN_DELETE(evq_trans_mgr); delete evq_trans_mgr; MEMMAN_DELETE(evq_sender); delete evq_sender; MEMMAN_DELETE(evq_trans_layer); delete evq_trans_layer; MEMMAN_DELETE(evq_timekeeper); delete evq_timekeeper; if (translator) { MEMMAN_DELETE(translator); delete translator; translator = NULL; } if (appTranslator) { MEMMAN_DELETE(appTranslator); delete(appTranslator); } if (qa) { MEMMAN_DELETE(qa); delete qa; } // Report memory leaks // Report deletion of log_file and sys_config already to get a correct // report. MEMMAN_DELETE(sys_config); MEMMAN_DELETE(log_file); MEMMAN_DELETE(memman); MEMMAN_REPORT; delete log_file; delete memman; sys_config->delete_lock_file(); delete sys_config; } twinkle-1.10.1/src/gui/messageform.cpp000066400000000000000000000404301277565361200176550ustar00rootroot00000000000000 /* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifdef HAVE_KDE #include #include #include #include #include #else #include "utils/mime_database.h" //Added by qt3to4: #include #include #include #endif #include "gui.h" #include "sockets/url.h" #include #include "audits/memman.h" #include "util.h" #include #include #include #include "utils/file_utils.h" #include #include "sendfileform.h" #include #include #include #include "messageform.h" using namespace utils; /** Maximum width for an inline image */ #define MAX_WIDTH_IMG_INLINE 400 /** Maximum height for an inline image */ #define MAX_HEIGHT_IMG_INLINE 400 #define IMG_SCALE_FACTOR(width, height) (std::min( float(MAX_WIDTH_IMG_INLINE) / (width), float(MAX_HEIGHT_IMG_INLINE) / (height) ) ) MessageForm::MessageForm(QWidget* parent) : QMainWindow(parent) { setupUi(this); (void)statusBar(); init(); } /* * Destroys the object and frees any allocated resources */ MessageForm::~MessageForm() { destroy(); // no need to delete child widgets, Qt does it all for us } /* * Sets the strings of the subwidgets using the current * language. */ void MessageForm::languageChange() { retranslateUi(this); } void MessageForm::init() { this->setAttribute(Qt::WA_DeleteOnClose); _getAddressForm = 0; _remotePartyComplete = false; // Add label to display size of typed message _msgSizeLabel = new QLabel(this); statusBar()->addWidget(_msgSizeLabel); showMessageSize(); // Set toolbutton icons for disabled options. setDisabledIcon(addressToolButton, "kontact_contacts-disabled.png"); attachmentPopupMenu = new QMenu(this); MEMMAN_NEW(attachmentPopupMenu); connect(attachmentPopupMenu, SIGNAL(triggered(QAction*)), this, SLOT(attachmentPopupActivated(QAction*))); _serviceMap = NULL; _saveAsDialog = NULL; _isComposingLabel = NULL; // When the user edits the message, the composition indication // will be set to active. connect(msgLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(setLocalComposingIndicationActive())); } void MessageForm::destroy() { if (_getAddressForm) { MEMMAN_DELETE(_getAddressForm); delete _getAddressForm; } MEMMAN_DELETE(attachmentPopupMenu); delete attachmentPopupMenu; #ifdef HAVE_KDE vector *serviceMap = (vector *)_serviceMap; if (serviceMap) { MEMMAN_DELETE(serviceMap); delete serviceMap; } #endif if (_saveAsDialog) { MEMMAN_DELETE(_saveAsDialog); delete _saveAsDialog; } if (_isComposingLabel) { MEMMAN_DELETE(_isComposingLabel); delete _isComposingLabel; } } void MessageForm::closeEvent(QCloseEvent *e) { MEMMAN_DELETE(this); // destructive close QMainWindow::closeEvent(e); } void MessageForm::show() { if (toLineEdit->text().isEmpty()) { sendFileAction->setEnabled(false); toLineEdit->setFocus(); } else { // Once a message session has been created, the // source and destination cannot be changed anymore. fromComboBox->setEnabled(false); toLineEdit->setEnabled(false); addressToolButton->setEnabled(false); sendFileAction->setEnabled(true); msgLineEdit->setFocus(); } QMainWindow::show(); } void MessageForm::selectUserConfig(t_user *user_config) { for (int i = 0; i < fromComboBox->count(); i++) { if (fromComboBox->itemText(i) == user_config->get_profile_name().c_str()) { fromComboBox->setCurrentIndex(i); break; } } } void MessageForm::showAddressBook() { if (!_getAddressForm) { _getAddressForm = new GetAddressForm(this); _getAddressForm->setModal(true); MEMMAN_NEW(_getAddressForm); } connect(_getAddressForm, SIGNAL(address(const QString &)), this, SLOT(selectedAddress(const QString &))); _getAddressForm->show(); } void MessageForm::selectedAddress(const QString &address) { toLineEdit->setText(address); } /** * Check if there is a valid sender and receiver. If so, then set the sender * and receiver addresses in the message session object. */ bool MessageForm::updateMessageSession() { string display, dest_str; t_user *from_user = phone->ref_user_profile( fromComboBox->currentText().toStdString()); if (!from_user) { // The user profile is not active anymore fromComboBox->setFocus(); return false; } ui->expand_destination(from_user, toLineEdit->text().trimmed().toStdString(), display, dest_str); t_url dest(dest_str); if (!dest.is_valid()) { toLineEdit->setFocus(); toLineEdit->selectAll(); return false; } _msgSession->set_user(from_user); _msgSession->set_remote_party(t_display_url(dest, display)); setRemotePartyCaption(); return true; } /** * Determine if a message can be sent, i.e. there is a valid sender and * receiver. If a message can be sent, then lock the sender and receiver. * @return True if message can be sent, false otherwise. */ bool MessageForm::prepareSendMessage() { if (toLineEdit->text().isEmpty()) { // No recipient selected. return false; } if (toLineEdit->isEnabled()) { if (!updateMessageSession()) { // No valid sender and receiver. return false; } // Once a message session has been created, the // source and destination cannot be changed anymore. fromComboBox->setEnabled(false); toLineEdit->setEnabled(false); addressToolButton->setEnabled(false); } return true; } /** Send a text message */ void MessageForm::sendMessage() { if (msgLineEdit->text().isEmpty()) { // Nothing to send return; } if (!prepareSendMessage()) { return; } _msgSession->send_msg(msgLineEdit->text().toStdString(), im::TXT_PLAIN); } /** * Send a file. * @param filename [in] Name of file to send. * @param subject [in] Subject to set in the message. */ void MessageForm::sendFile(const QString &filename, const QString &subject) { if (!prepareSendMessage()) { return; } t_media media("application/octet-stream"); #ifdef HAVE_KDE // Get mime type for the attachment KMimeType::Ptr pMime = KMimeType::findByURL(filename); media = t_media(pMime->name().ascii()); #else string mime_type = mime_database->get_mimetype(filename.toStdString()); if (!mime_type.empty()) { media = t_media(mime_type); } #endif _msgSession->send_file(filename.toStdString(), media, subject.toStdString()); } /** * Add a message to the converstation broswer. * @param msg [in] The message to add. * @param name [in] The name of the sender of the message. */ void MessageForm::addMessage(const im::t_msg &msg, const QString &name) { QString s = ""; // Timestamp and name of sender if (msg.direction == im::MSG_DIR_IN) s += ""; s += time2str(msg.timestamp, "%H:%M:%S ").c_str(); #if QT_VERSION >= 0x050000 s += name.toHtmlEscaped(); #else s += Qt::escape(name); #endif if (msg.direction == im::MSG_DIR_IN) s += ""; s += ""; // Subject if (!msg.subject.empty()) { s += "
"; s += ""; s += "Subject: "; s += ""; s += msg.subject.c_str(); } // Text message if (!msg.message.empty()) { s += "
"; if (msg.format == im::TXT_HTML) { s += msg.message.c_str(); } else { #if QT_VERSION >= 0x050000 s += QString::fromStdString(msg.message).toHtmlEscaped(); #else s += Qt::escape(msg.message.c_str()); #endif } } // Attachment if (msg.has_attachment) { s += "
"; s += "
"; bool show_attachment_inline = false; bool scale_image = false; int scaled_width = 0, scaled_height = 0; if (msg.attachment_media.type == "image") { // Show image inline if possible QPixmap image (msg.attachment_filename.c_str()); if (!image.isNull()) { show_attachment_inline = true; if (image.width() > MAX_WIDTH_IMG_INLINE && image.height() > MAX_HEIGHT_IMG_INLINE) { // Shrink image scaled_width = int(image.width() * IMG_SCALE_FACTOR(image.width(), image.height())); scaled_height = int(image.height() * IMG_SCALE_FACTOR(image.width(), image.height())); scale_image = true; } } } s += ""; s+= "
"; s += msg.attachment_save_as_name.c_str(); s += "
"; if (scale_image) { s += "
"; s += ""; s += tr("image size is scaled down in preview"); s += ""; } // Store the association between the tempory file name of the // save attachment and the suggested save-as file name. When // the user clicks on the attachment, only the temporary file name // is available. Through this association the suggested file name // can be retrieved. _filenameMap[msg.attachment_filename] = msg.attachment_save_as_name; } conversationBrowser->append(s); } void MessageForm::displayError(const QString &errorMsg) { QString s = ""; s += ""; s += tr("Delivery failure"); s += ": "; #if QT_VERSION >= 0x050000 s += errorMsg.toHtmlEscaped(); #else s += Qt::escape(errorMsg); #endif s += ""; conversationBrowser->append(s); } void MessageForm::displayDeliveryNotification(const QString ¬ification) { QString s = ""; s += ""; s += tr("Delivery notification"); s += ": "; #if QT_VERSION >= 0x050000 s += notification.toHtmlEscaped(); #else s += Qt::escape(notification); #endif s += ""; conversationBrowser->append(s); } void MessageForm::setRemotePartyCaption(void) { if (!_msgSession) return; t_user *user = _msgSession->get_user(); t_display_url remote_party = _msgSession->get_remote_party(); setWindowTitle(ui->format_sip_address(user, remote_party.display, remote_party.url).c_str()); } void MessageForm::showAttachmentPopupMenu(const QUrl &attachment) { #ifdef HAVE_KDE vector *serviceMap = (vector *)_serviceMap; if (serviceMap) { MEMMAN_DELETE(serviceMap); delete serviceMap; } serviceMap = new vector; MEMMAN_NEW(serviceMap); _serviceMap = (void *)serviceMap; #endif int id = 0; // Identity of popup menu item // Store attachment. When the user selects an attachment we still // know which attachment was clicked. clickedAttachment = attachment.toLocalFile(); attachmentPopupMenu->clear(); QIcon saveIcon(QPixmap(":/icons/images/save_as.png")); attachmentPopupMenu->addAction(saveIcon, "Save as...")->setData(id++); #ifdef HAVE_KDE // Get mime type for the attachment KMimeType::Ptr pMime = KMimeType::findByURL(attachment); // Get applications that can open the mime type KServiceTypeProfile::OfferList services = KServiceTypeProfile::offers( pMime->name(), "Application"); KServiceTypeProfile::OfferList::ConstIterator it; for (it = services.begin(); it != services.end(); ++it) { KService::Ptr service = (*it).service(); serviceMap->push_back(service); QString menuText = tr("Open with %1...").arg(service->name()); QPixmap pixmap = service->pixmap(KIcon::Small); QIcon iconSet; iconSet.setPixmap(pixmap, QIcon::Small); attachmentPopupMenu->insertItem(iconSet, menuText, id++); } QIcon openIcon(QPixmap(":/icons/images/fileopen.png")); attachmentPopupMenu->insertItem(openIcon, tr("Open with..."), id++); #endif attachmentPopupMenu->popup(QCursor::pos(), 0); } void MessageForm::attachmentPopupActivated(QAction* act) { int id = act->data().toInt(); #ifdef HAVE_KDE vector *serviceMap = (vector *)_serviceMap; assert(serviceMap); #endif if (id == 0) { #ifdef HAVE_KDE KFileDialog *d = new KFileDialog(QString::null, QString::null, this, 0, true); MEMMAN_NEW(d); d->setOperationMode(KFileDialog::Saving); connect(d, SIGNAL(okClicked()), this, SLOT(saveAttachment())); #else QFileDialog *d = new QFileDialog(this); MEMMAN_NEW(d); connect(d, SIGNAL(fileSelected(const QString &)), this, SLOT(saveAttachment())); #endif d->selectFile(QString::fromStdString(_filenameMap[clickedAttachment.toStdString()])); d->setWindowTitle(tr("Save attachment as...")); if (_saveAsDialog) { MEMMAN_DELETE(_saveAsDialog); delete _saveAsDialog; } _saveAsDialog = d; d->show(); #ifdef HAVE_KDE } else if (id > serviceMap->size()) { KURL::List urls; urls << clickedAttachment; KRun::displayOpenWithDialog(urls, false); } else { KURL::List urls; urls << clickedAttachment; KRun::run(*serviceMap->at(id-1), urls); #endif } } void MessageForm::saveAttachment() { #ifdef HAVE_KDE KFileDialog *d = dynamic_cast(_saveAsDialog); #else QFileDialog *d = dynamic_cast(_saveAsDialog); #endif QStringList files = d->selectedFiles(); QString filename; if (!files.empty()) filename = files[0]; if (QFile::exists(filename)) { bool overwrite = ((t_gui *)ui)->cb_ask_msg(this, tr("File already exists. Do you want to overwrite this file?").toStdString(), MSG_WARNING); if (!overwrite) return; } if (!filecopy(clickedAttachment.toStdString(), filename.toStdString())) { ((t_gui *)ui)->cb_show_msg(this, tr("Failed to save attachment.").toStdString(), MSG_CRITICAL); } } /** Choose a file to send */ void MessageForm::chooseFileToSend() { // Indicate that a message is being composed. setLocalComposingIndicationActive(); SendFileForm *form = new SendFileForm(this); MEMMAN_NEW(form); // Form will auto destruct itself on close. connect(form, SIGNAL(selected(const QString &, const QString &)), this, SLOT(sendFile(const QString &, const QString &))); form->show(); } /** * Show an is-composing indication in the status bar. * @param name [in] The name of the sender of the message. */ void MessageForm::setComposingIndication(const QString &name) { if (!_isComposingLabel) { _isComposingLabel = new QLabel(NULL); MEMMAN_NEW(_isComposingLabel); _isComposingLabel->setText(tr("%1 is typing a message.").arg(name)); _isComposingLabel->setFrameStyle(QFrame::NoFrame | QFrame::Plain); statusBar()->addWidget(_isComposingLabel); } } /** Clear an is-composing indication from the status bar. */ void MessageForm::clearComposingIndication() { if (_isComposingLabel) { statusBar()->removeWidget(_isComposingLabel); statusBar()->clearMessage(); MEMMAN_DELETE(_isComposingLabel); delete _isComposingLabel; _isComposingLabel = NULL; } } /** Set the local composition indication to active. */ void MessageForm::setLocalComposingIndicationActive() { _msgSession->set_local_composing_state(im::COMPOSING_STATE_ACTIVE); } void MessageForm::keyPressEvent(QKeyEvent *e) { switch (e->key()) { case Qt::Key_Return: case Qt::Key_Enter: if (sendPushButton->isEnabled()) { sendPushButton->animateClick(); } break; default: e->ignore(); } } void MessageForm::toAddressChanged(const QString &address) { sendFileAction->setEnabled(!address.isEmpty()); } /** Show the size of the typed message */ void MessageForm::showMessageSize() { uint len = msgLineEdit->text().length(); QString s(tr("Size")); s += ": "; s += QString().setNum(len); _msgSizeLabel->setText(s); } twinkle-1.10.1/src/gui/messageform.h000066400000000000000000000037351277565361200173310ustar00rootroot00000000000000#ifndef MESSAGEFORM_H #define MESSAGEFORM_H #include "getaddressform.h" #include "im/msg_session.h" #include "phone.h" #include #include #include "textbrowsernoautolink.h" #include "user.h" #include "ui_messageform.h" #include #include class t_phone; extern t_phone *phone; class MessageForm : public QMainWindow, public Ui::MessageForm { Q_OBJECT public: MessageForm(QWidget* parent = 0); ~MessageForm(); virtual bool updateMessageSession(); virtual bool prepareSendMessage(); public slots: virtual void closeEvent( QCloseEvent * e ); virtual void show(); virtual void selectUserConfig( t_user * user_config ); virtual void showAddressBook(); virtual void selectedAddress( const QString & address ); virtual void sendMessage(); virtual void sendFile( const QString & filename, const QString & subject ); virtual void addMessage( const im::t_msg & msg, const QString & name ); virtual void displayError( const QString & errorMsg ); virtual void displayDeliveryNotification( const QString & notification ); virtual void setRemotePartyCaption( void ); virtual void showAttachmentPopupMenu( const QUrl & attachment ); virtual void attachmentPopupActivated( QAction* action ); virtual void saveAttachment(); virtual void chooseFileToSend(); virtual void setComposingIndication( const QString & name ); virtual void clearComposingIndication(); virtual void setLocalComposingIndicationActive(); virtual void keyPressEvent( QKeyEvent * e ); virtual void toAddressChanged( const QString & address ); virtual void showMessageSize(); protected: im::t_msg_session *_msgSession; protected slots: virtual void languageChange(); private: QDialog *_saveAsDialog; map _filenameMap; bool _remotePartyComplete; GetAddressForm *_getAddressForm; QMenu *attachmentPopupMenu; QString clickedAttachment; void *_serviceMap; QLabel *_isComposingLabel; QLabel *_msgSizeLabel; void init(); void destroy(); }; #endif twinkle-1.10.1/src/gui/messageform.ui000066400000000000000000000231271277565361200175140ustar00rootroot00000000000000 MessageForm 0 0 578 356 Twinkle - Instant message &To: toLineEdit false The user that will send the message. The address of the user that you want to send a message. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Qt::TabFocus F10 :/icons/images/kontact_contacts.png Address book Select an address from the address book. &User profile: fromComboBox false Conversation Type your message here and then press "send" to send it. &Send Alt+S false true Send the message. :/icons/images/attach.png Send file... Send file TextBrowserNoAutoLink QWidget
textbrowsernoautolink.h
-1 -1 0 7 7 image0
89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b000003b149444154388dad945f4c5b551cc73fe7dc4b7b4bcba0762d45c43114323599ee6192609c51d883892ce083f1718b3ebb185f8dc91e972cf39d2d2a2f1af664b6f1e0fe3863a0718969700eb0c52142da0242a1bd6d696f7bcff101585203ceb8fd9ece39f99dcff9fe7edf939f88c562ec465f5f9fe609442c161362173c3e3eae7b7a7ac8e7f36432196cdbfe4f907c3e4f2291201e8fe338cec3737357e9e8e828aded1e229d650e1f2d51754b082110124c13a4dc5ea341eb9dc284c0558a853f3ce8cb0677ef500fde7d39d2596679e326597b8e9abb85d7a770ab16ab6983ec5a05b487a70e36f0f4e10afe408d6a558310980108478dba4a1e8233990c5d474b64ed39aa3a8fe5f3317fbf81dbd70bccfeb205947632fd74f6589c1c6ea2f70d03a58ba0c1f2c9bdc1b66de3b8256a6e11cbe7e3ee1d181b590124fe2693aeee08d223c82c3a2c24b7b874bec8f26288774f7bd054504aef0dde6e99c0eb83f9fb266323cb80a27fb0958141836044605a2ee5523393371cc646fee2da37195aa35d0c0c5b4859ac03d7e91712dcaac5adab3650a3ff9d08ef7dd8404bb48869e5d958b5b87dadc4c9a1464e9f0d0326df7ebd86bd2e310cb1bf62d384d59441f2d70a070e1c60e09489929b988681bdd9cc97170bcc4c65595f71f8e0e3301337fc24a7732467831875a47f289652b0be5e4151e6d07316c1b0c0340d8ab92023e76d66a6b2840e36d2fb7a13fee632475e6edc367ea98a90fb98b7dd6310ca0328a44761582e1bab41befabcc0ec940d28bc5e93b68e064cab84e1d9beaeb48934eac1f53b01c1b000fca496aa54b61a99fcde61662a4b4b4b23d1680be9d426173e4df3602a48ea411989a4fd590f52a8fd156b05ed9d350e3defe3cfdf4b4c7ce770ea7d3fb9f520afbe1620daeee5c26735d20b9b9cfb6811a754a439e4e5c5639a4caa1e5caf586bfc0197b78702005cb9b4cae4cd3267ce8638fe964bd72b393e39d74928d242617303a756a37f284447770dcdbffc6384a05a85de1306e9a52057c7527c7131c3c42d3f475eb2303c82d4fc3276d6811db37efeb148723082d9b08f79f97c1e5729109a9a28307cc622d2d6cdf52b2b24efe548dedb00142009862cfa879ee1a71f6cec928353511472fbf4389148b0b0e0c108081412458dfe21c9f11351e67e7358595468246d1d1e5e38a6e9e851bc39d84ab502a669331dafec0d8ec7e3e8cb06e1a881d727d1ae40180a434a8c9db129a54126ad48a7358c2b4c5352c8c374bcccdab2bb37d8719cba79fab8211f9df218e0582c261e95f8bfc04f1a1e8bc5c4dfe0a190172af6a9690000000049454e44ae426082 ui_getaddressform.h qstring.h user.h im/msg_session.h phone.h qlabel.h textbrowsernoautolink.h sendPushButton clicked() MessageForm sendMessage() addressToolButton clicked() MessageForm showAddressBook() conversationBrowser anchorClicked(QUrl) MessageForm showAttachmentPopupMenu(QUrl) sendFileAction triggered() MessageForm chooseFileToSend() toLineEdit textChanged(QString) MessageForm toAddressChanged(QString) msgLineEdit textChanged(QString) MessageForm showMessageSize()
twinkle-1.10.1/src/gui/messageformview.cpp000066400000000000000000000105171277565361200205530ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "messageformview.h" #include "gui.h" #include "audits/memman.h" MessageFormView::MessageFormView(QWidget *parent, im::t_msg_session *s) : MessageForm(parent), t_observer() { _msgSession = s; _msgSession->attach(this); _destructing = false; connect(this, SIGNAL(update_signal()), this, SLOT(update_slot())); } MessageFormView::~MessageFormView() { _destructing = true; if (_msgSession) { ((t_gui *)ui)->removeMessageSession(_msgSession); MEMMAN_DELETE(_msgSession); delete _msgSession; } } void MessageFormView::updatePartyInfo(void) { t_user *user_config = _msgSession->get_user(); selectUserConfig(user_config); t_display_url to_url = _msgSession->get_remote_party(); if (to_url.is_valid()) { toLineEdit->setText(ui->format_sip_address(user_config, to_url.display, to_url.url).c_str()); } else { toLineEdit->clear(); } } void MessageFormView::update_slot(void) { // Called directly from core, so lock GUI ui->lock(); updatePartyInfo(); setRemotePartyCaption(); t_user *user_config = _msgSession->get_user(); t_display_url to_url = _msgSession->get_remote_party(); // Update msgLineEdit field based on msg-in-flight indication if (!_msgSession->is_msg_in_flight() && !msgLineEdit->isEnabled()) { msgLineEdit->clear(); // When the user edits the message, the composition indication // will be set to active. connect(msgLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(setLocalComposingIndicationActive())); // Enable msgLineEdit first, otherwise the setFocus does not work msgLineEdit->setEnabled(true); msgLineEdit->setFocus(); } else if (_msgSession->is_msg_in_flight() && msgLineEdit->isEnabled()) { // Disable the triggering of the composition indication while a message // is being sent. disconnect(msgLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(setLocalComposingIndicationActive())); msgLineEdit->setText(tr("sending message")); } // Enable/disable msgLineEdit here to be robust, such that msgLineEdit // does not stay disabled forever. msgLineEdit->setEnabled(!_msgSession->is_msg_in_flight()); sendFileAction->setEnabled(!_msgSession->is_msg_in_flight()); // Display error if (_msgSession->error_received()) { string error_msg = _msgSession->take_error(); displayError(error_msg.c_str()); } // Display delivery notification if (_msgSession->delivery_notification_received()) { string notification = _msgSession->take_delivery_notification(); displayDeliveryNotification(notification.c_str()); } // Display message composing indication if (_msgSession->get_remote_composing_state() == im::COMPOSING_STATE_ACTIVE) { QString name = to_url.display.c_str(); if (name.isEmpty()) { name = to_url.url.get_user().c_str(); } setComposingIndication(name); } else { clearComposingIndication(); } // Display message if (_msgSession->is_new_message_added()) { im::t_msg m; try { m = _msgSession->get_last_message(); } catch (empty_list_exception) { ui->unlock(); return; } QString name; if (m.direction == im::MSG_DIR_IN) { name = to_url.display.c_str(); if (name.isEmpty()) { name = to_url.url.get_user().c_str(); } } else { name = user_config->get_display(false).c_str(); if (name.isEmpty()) { name = user_config->get_name().c_str(); } } addMessage(m, name); } ui->unlock(); } void MessageFormView::update(void) { emit update_signal(); } void MessageFormView::subject_destroyed() { _msgSession = NULL; if (!_destructing) close(); } void MessageFormView::show() { ((t_gui *)ui)->fill_user_combo(fromComboBox); updatePartyInfo(); MessageForm::show(); } twinkle-1.10.1/src/gui/messageformview.h000066400000000000000000000026001277565361200202120ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _MESSAGEFORMVIEW_H #define _MESSAGEFORMVIEW_H #include "messageform.h" #include "im/msg_session.h" #include "patterns/observer.h" class MessageFormView : public MessageForm, patterns::t_observer { Q_OBJECT private: bool _destructing; /**< Indicates if object is being destructed. */ public: MessageFormView(QWidget *parent, im::t_msg_session *s); virtual ~MessageFormView(); virtual void updatePartyInfo(void); /** Update the message form with the latest message session state. */ virtual void update(void); virtual void subject_destroyed(void); virtual void show(void); signals: void update_signal(); private slots: void update_slot(); }; #endif twinkle-1.10.1/src/gui/mphoneform.cpp000066400000000000000000002625431277565361200175320ustar00rootroot00000000000000/**************************************************************************** ** ui.h extension file, included from the uic-generated form implementation. ** ** If you wish to add, delete or rename functions or slots use ** Qt Designer which will update this file, pres:erving your code. Ceate an ** init() function in place of a constructor, and a destroy() function in ** place of a destructor. *****************************************************************************/ /* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "mphoneform.h" #include "twinkle_config.h" #include #include #include #include #include #include #include #include #include #include "../audits/memman.h" #include "user.h" #include #include #include #include #include "gui.h" #include #include #include #include "audits/memman.h" #include "line.h" #include "stun/stun_transaction.h" #include "log.h" #include #include "util.h" #include #include #include #include #include #include "buddyform.h" #include "diamondcardprofileform.h" #include "osd.h" #include "incoming_call_popup.h" extern QSettings* g_gui_state; // Time (s) that the conversation timer of a line should stay visible after // a call has ended #define HIDE_LINE_TIMER_AFTER 5 MphoneForm::MphoneForm(QWidget* parent) : QMainWindow(parent) { setupUi(this); init(); } MphoneForm::~MphoneForm() { destroy(); } void MphoneForm::init() { // Forms dtmfForm = 0; inviteForm = 0; redirectForm = 0; transferForm = 0; termCapForm = 0; srvRedirectForm = 0; userProfileForm = 0; sysSettingsForm = 0; logViewForm = 0; historyForm = 0; selectUserForm = 0; selectProfileForm = 0; getAddressForm = 0; sysTray = 0; // Popup menu for a single buddy QIcon inviteIcon(QPixmap(":/icons/images/invite.png")); QIcon messageIcon(QPixmap(":/icons/images/message.png")); QIcon editIcon(QPixmap(":/icons/images/edit16.png")); QIcon deleteIcon(QPixmap(":/icons/images/editdelete.png")); buddyPopupMenu = new QMenu(this); MEMMAN_NEW(buddyPopupMenu); buddyPopupMenu->addAction(inviteIcon, tr("&Call..."), this, SLOT(doCallBuddy())); buddyPopupMenu->addAction(messageIcon, tr("Instant &message..."), this, SLOT(doMessageBuddy())); buddyPopupMenu->addAction(editIcon, tr("&Edit..."), this, SLOT(doEditBuddy())); buddyPopupMenu->addAction(deleteIcon, tr("&Delete"), this, SLOT(doDeleteBuddy())); // Popup menu for a buddy list (click on profile name) QIcon changeAvailabilityIcon(QPixmap(":/icons/images/presence_online.png")); QIcon addIcon(QPixmap(":/icons/images/buddy.png")); buddyListPopupMenu = new QMenu(this); MEMMAN_NEW(buddyListPopupMenu); changeAvailabilityPopupMenu = buddyListPopupMenu->addMenu(changeAvailabilityIcon, tr("&Change availability")); // Change availibility sub popup menu QIcon availOnlineIcon(QPixmap(":/icons/images/presence_online.png")); QIcon availOfflineIcon(QPixmap(":/icons/images/presence_offline.png")); changeAvailabilityPopupMenu->addAction(availOfflineIcon, tr("O&ffline"), this, SLOT(doAvailabilityOffline())); changeAvailabilityPopupMenu->addAction(availOnlineIcon, tr("&Online"), this, SLOT(doAvailabilityOnline())); buddyListPopupMenu->addAction(addIcon, tr("&Add buddy..."), this, SLOT(doAddBuddy())); // ToDo: Tool tip for buddy list // Line timers lineTimer1 = 0; lineTimer2 = 0; timer1TextLabel->hide(); timer2TextLabel->hide(); // Timer to hide the conversation timer after a conversation has ended. hideLineTimer1 = new QTimer(this); MEMMAN_NEW(hideLineTimer1); hideLineTimer2 = new QTimer(this); MEMMAN_NEW(hideLineTimer2); connect(hideLineTimer1, SIGNAL(timeout()), timer1TextLabel, SLOT(hide())); connect(hideLineTimer2, SIGNAL(timeout()), timer2TextLabel, SLOT(hide())); // Attach the MWI flash slot to the MWI flash timer connect(&tmrFlashMWI, SIGNAL(timeout()), this, SLOT(flashMWI())); // Set toolbar icons for disabled options. setDisabledIcon(callInvite, "invite-disabled.png"); setDisabledIcon(callAnswer, "answer-disabled.png"); setDisabledIcon(callBye, "bye-disabled.png"); setDisabledIcon(callReject, "reject-disabled.png"); setDisabledIcon(callRedirect, "redirect-disabled.png"); setDisabledIcon(callTransfer, "transfer-disabled.png"); setDisabledIcon(callHold, "hold-disabled.png"); setDisabledIcon(callConference, "conf-disabled.png"); setDisabledIcon(callMute, "mute-disabled.png"); setDisabledIcon(callDTMF, "dtmf-disabled.png"); setDisabledIcon(callRedial, "redial-disabled.png"); // Set tool button icons for disabled options setDisabledIcon(addressToolButton, "kontact_contacts-disabled.png"); // Some text labels on the main window are implemented as QLineEdit // objects as these do not automatically resize when a text set with setText // does not fit. The background of a QLineEdit is static however, it does not // automatically take a background color passed by the -bg parameter. // Set the background color of these QLineEdit objects here. //from1Label->setPaletteBackgroundColor(paletteBackgroundColor()); //to1Label->setPaletteBackgroundColor(paletteBackgroundColor()); //subject1Label->setPaletteBackgroundColor(paletteBackgroundColor()); //from2Label->setPaletteBackgroundColor(paletteBackgroundColor()); //to2Label->setPaletteBackgroundColor(paletteBackgroundColor()); //subject2Label->setPaletteBackgroundColor(paletteBackgroundColor()); osdWindow = new OSD(this); connect(osdWindow, SIGNAL(hangupClicked()), this, SLOT(phoneBye())); connect(osdWindow, SIGNAL(muteClicked()), this, SLOT(osdMuteClicked())); incomingCallPopup = new IncomingCallPopup(this); connect(incomingCallPopup, SIGNAL(answerClicked()), this, SLOT(phoneAnswer())); connect(incomingCallPopup, SIGNAL(rejectClicked()), this, SLOT(phoneReject())); // A QComboBox accepts a new line through copy/paste. QRegExp rxNoNewLine("[^\\n\\r]*"); callComboBox->setValidator(new QRegExpValidator(rxNoNewLine, this)); if (sys_config->get_gui_use_systray()) { // Create system tray icon sysTray = new QSystemTrayIcon(this); sysTray->setIcon( QPixmap(":/icons/images/sys_idle_dis.png")); sysTray->setToolTip(PRODUCT_NAME); // Add items to the system tray menu QMenu *menu; menu = new QMenu(this); sysTray->setContextMenu(menu); connect(sysTray, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(sysTrayIconClicked(QSystemTrayIcon::ActivationReason))); // Call menu menu->addAction(callInvite); menu->addAction(callAnswer); menu->addAction(callBye); menu->addAction(callReject); menu->addAction(callRedirect); menu->addAction(callTransfer); menu->addAction(callHold); menu->addAction(callConference); menu->addAction(callMute); menu->addAction(callDTMF); menu->addAction(callRedial); menu->addSeparator(); // Messaging menu->addAction(actionSendMsg); menu->addSeparator(); // Line activation menu->addActions(actgrActivateLine->actions()); menu->addSeparator(); // Service menu menu->addAction(serviceDnd); menu->addAction(serviceRedirection); menu->addAction(serviceAutoAnswer); menu->addAction(servicesVoice_mailAction); menu->addSeparator(); // View menu menu->addAction(viewCall_HistoryAction); menu->addSeparator(); #ifdef WITH_DIAMONDCARD // Diamondcard menu menu->insertItem("Diamondcard", Diamondcard); menu->addSeparator(); #endif menu->addAction(fileExitAction); sysTray->show(); } #ifndef WITH_DIAMONDCARD Diamondcard->menuAction()->setVisible(false); #endif restoreState(g_gui_state->value("mainwindow/state").toByteArray()); restoreGeometry(g_gui_state->value("mainwindow/geometry").toByteArray()); splitter2->restoreState(g_gui_state->value("mainwindow/mainsplitter").toByteArray()); } void MphoneForm::destroy() { g_gui_state->setValue("mainwindow/state", saveState()); g_gui_state->setValue("mainwindow/geometry", saveGeometry()); g_gui_state->setValue("mainwindow/mainsplitter", splitter2->saveState()); if (dtmfForm) { MEMMAN_DELETE(dtmfForm); delete dtmfForm; } if (inviteForm) { MEMMAN_DELETE(inviteForm); delete inviteForm; } if (redirectForm) { MEMMAN_DELETE(redirectForm); delete redirectForm; } if (termCapForm) { MEMMAN_DELETE(termCapForm); delete termCapForm; } if (srvRedirectForm) { MEMMAN_DELETE(srvRedirectForm); delete srvRedirectForm; } if (userProfileForm) { MEMMAN_DELETE(userProfileForm); delete userProfileForm; } if (transferForm) { MEMMAN_DELETE(transferForm); delete transferForm; } if (sysSettingsForm) { MEMMAN_DELETE(sysSettingsForm); delete sysSettingsForm; } if (logViewForm) { if (logViewForm->isVisible()) logViewForm->close(); MEMMAN_DELETE(logViewForm); delete logViewForm; } if (historyForm) { if (historyForm->isVisible()) historyForm->close(); MEMMAN_DELETE(historyForm); delete historyForm; } if (selectUserForm) { MEMMAN_DELETE(selectUserForm); delete selectUserForm; } if (selectProfileForm) { MEMMAN_DELETE(selectProfileForm); delete selectProfileForm; } if (getAddressForm) { MEMMAN_DELETE(getAddressForm); delete getAddressForm; } if (sysTray) { MEMMAN_DELETE(sysTray); delete sysTray; } if (lineTimer1) { MEMMAN_DELETE(lineTimer1); delete lineTimer1; } if (lineTimer2) { MEMMAN_DELETE(lineTimer2); delete lineTimer2; } MEMMAN_DELETE(hideLineTimer1); delete hideLineTimer1; MEMMAN_DELETE(hideLineTimer2); delete hideLineTimer2; MEMMAN_DELETE(buddyPopupMenu); delete buddyPopupMenu; MEMMAN_DELETE(buddyListPopupMenu); delete buddyListPopupMenu; } QString MphoneForm::lineSubstate2str( int line) { QString reason; t_call_info call_info = phone->get_call_info(line); switch(phone->get_line_substate(line)) { case LSSUB_IDLE: return tr("idle"); case LSSUB_SEIZED: return tr("dialing"); case LSSUB_OUTGOING_PROGRESS: reason = call_info.last_provisional_reason.c_str(); if (reason == "") { return tr("attempting call, please wait"); } return reason; case LSSUB_INCOMING_PROGRESS: return QString("") + tr("incoming call") + ""; case LSSUB_ANSWERING: return tr("establishing call, please wait"); case LSSUB_ESTABLISHED: if (phone->has_line_media(line)) { return tr("established"); } else { return tr("established (waiting for media)"); } break; case LSSUB_RELEASING: return tr("releasing call, please wait"); default: return tr("unknown state"); } } void MphoneForm::closeEvent( QCloseEvent *e ) { if (sysTray && sys_config->get_gui_hide_on_close()) { hide(); } else { fileExit(); } } void MphoneForm::fileExit() { hide(); qApp->quit(); } // Append a string to the display window void MphoneForm::display( const QString &s ) { displayContents.push_back(s); if (displayContents.size() > 100) { displayContents.pop_front(); } displayTextEdit->setText(displayContents.join("\n")); // Set cursor position at the end of text QTextCursor cursor; cursor = displayTextEdit->textCursor(); cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); displayTextEdit->setTextCursor(cursor); } // Print message header on display void MphoneForm::displayHeader() { display(""); display(current_time2str("%a %H:%M:%S").c_str()); } // Update the conversation timer void MphoneForm::showLineTimer(int line) { struct timeval t; gettimeofday(&t, NULL); QLabel *timerLabel; if (line == 0) { timerLabel = timer1TextLabel; } else { timerLabel = timer2TextLabel; } // Calculate duration of call t_call_record cr = phone->get_call_hist(line); unsigned long duration = t.tv_sec - cr.time_answer; timerLabel->setText(timer2str(duration).c_str()); updateOSD(); } void MphoneForm::showLineTimer1() { showLineTimer(0); } void MphoneForm::showLineTimer2() { showLineTimer(1); } // Update visibility of the conversation timer for a line // Initialize the timer for a new established call. void MphoneForm::updateLineTimer(int line) { QLabel *timerLabel; QTimer **timer; QTimer *hideLineTimer; if (line == 0) { timerLabel = timer1TextLabel; timer = &lineTimer1; hideLineTimer = hideLineTimer1; } else { timerLabel = timer2TextLabel; timer = &lineTimer2; hideLineTimer = hideLineTimer2; } t_line_substate line_substate = phone->get_line_substate(line); // Stop hide timer if necessary switch(line_substate) { case LSSUB_IDLE: case LSSUB_RELEASING: // Timer can be shown as long as line is idle or releasing. break; default: // The timer showing the call duration should only stay // for a few seconds as long as the line is idle or being // released. // If a new call arrives on the line, the hide timer should // be stopped, otherwise the timer of the new call will // automatically disappear. if (hideLineTimer->isActive()) { hideLineTimer->stop(); if (*timer == NULL) timerLabel->hide(); } break; } switch(line_substate) { case LSSUB_ESTABLISHED: // Initialize and show call duration timer if (*timer == NULL) { timerLabel->setText(timer2str(0).c_str()); timerLabel->show(); *timer = new QTimer(this); MEMMAN_NEW(*timer); if (line == 0) { connect(*timer, SIGNAL(timeout()), this, SLOT(showLineTimer1())); } else { connect(*timer, SIGNAL(timeout()), this, SLOT(showLineTimer2())); } // Update timer every 1s (*timer)->setSingleShot(false); (*timer)->start(1000); } break; default: // Hide call duration timer if (*timer != NULL) { // Hide the timer after a few seconds hideLineTimer->setSingleShot(true); hideLineTimer->start(HIDE_LINE_TIMER_AFTER * 1000); (*timer)->stop(); MEMMAN_DELETE(*timer); *timer = NULL; } break; } } void MphoneForm::updateLineEncryptionState(int line) { QLabel *cryptLabel, *sasLabel; if (line == 0) { cryptLabel = crypt1Label; sasLabel = line1SasLabel; } else { cryptLabel = crypt2Label; sasLabel = line2SasLabel; } t_audio_session *as = phone->get_line(line)->get_audio_session(); if (as && phone->is_line_encrypted(line)) { string zrtp_sas = as->get_zrtp_sas(); bool zrtp_sas_confirmed = as->get_zrtp_sas_confirmed(); string srtp_cipher_mode = as->get_srtp_cipher_mode(); cryptLabel->setToolTip(QString()); QString toolTip = tr("Voice is encrypted") + " ("; toolTip.append(srtp_cipher_mode.c_str()).append(")"); if (!zrtp_sas.empty()) { // Set tool tip on encryption icon toolTip.append("\nSAS = "); toolTip.append(zrtp_sas.c_str()); // Show SAS sasLabel->setText(zrtp_sas.c_str()); sasLabel->show(); } else { sasLabel->hide(); } if (!zrtp_sas_confirmed) { toolTip.append("\n").append(tr("Click to confirm SAS.")); cryptLabel->setFrameStyle(QFrame::Panel | QFrame::Raised); cryptLabel->setPixmap( QPixmap(":/icons/images/encrypted.png")); } else { toolTip.append("\n").append(tr("Click to clear SAS verification.")); cryptLabel->setFrameStyle(QFrame::NoFrame); cryptLabel->setPixmap( QPixmap(":/icons/images/encrypted_verified.png")); } cryptLabel->setToolTip(toolTip); cryptLabel->show(); } else { cryptLabel->hide(); sasLabel->hide(); } } void MphoneForm::updateLineStatus(int line) { QString state; bool on_hold; // indicates if a line is put on-hold bool in_conference; // indicates if a line is in a conference bool is_muted; // indicates is a line is muted t_refer_state refer_state; // indicates if a call transfer is in progress bool is_transfer_consult; // indicates if the call is a consultation bool to_be_transferred; // indicates if the line is to be transferred after consultation t_call_info call_info; unsigned short dummy; QLabel *statLabel, *holdLabel, *muteLabel, *confLabel, *referLabel, *statusTextLabel; if (line == 0) { statLabel = line1StatLabel; holdLabel = line1HoldLabel; muteLabel = line1MuteLabel; confLabel = line1ConfLabel; referLabel = line1ReferLabel; statusTextLabel = status1TextLabel; } else { statLabel = line2StatLabel; holdLabel = line2HoldLabel; muteLabel = line2MuteLabel; confLabel = line2ConfLabel; referLabel = line2ReferLabel; statusTextLabel = status2TextLabel; } state = lineSubstate2str(line); on_hold = phone->is_line_on_hold(line); if (on_hold) { holdLabel->show(); } else { holdLabel->hide(); } in_conference = phone->part_of_3way(line); if (in_conference) { confLabel->show(); } else { confLabel->hide(); } is_muted = phone->is_line_muted(line); if (is_muted) { muteLabel->show(); } else { muteLabel->hide(); } refer_state = phone->get_line_refer_state(line); is_transfer_consult = phone->is_line_transfer_consult(line, dummy); to_be_transferred = phone->line_to_be_transferred(line, dummy); if (refer_state != REFST_NULL || is_transfer_consult || to_be_transferred) { QString toolTip; referLabel->setToolTip(QString()); referLabel->show(); if (is_transfer_consult) { referLabel->setPixmap( QPixmap(":/icons/images/consult-xfer.png")); toolTip = tr("Transfer consultation"); } else { referLabel->setPixmap( QPixmap(":/icons/images/cf.png")); toolTip = tr("Transferring call"); } referLabel->setToolTip(toolTip); } else { referLabel->hide(); } statusTextLabel->setText(state); t_line_substate line_substate; line_substate = phone->get_line_substate(line); switch (line_substate) { case LSSUB_IDLE: ((t_gui *)ui)->clearLineFields(line); statLabel->hide(); break; case LSSUB_SEIZED: case LSSUB_OUTGOING_PROGRESS: statLabel->setPixmap(QPixmap(":/icons/images/stat_outgoing.png")); statLabel->show(); break; case LSSUB_INCOMING_PROGRESS: statLabel->setPixmap(QPixmap(":/icons/images/stat_ringing.png")); statLabel->show(); break; case LSSUB_ANSWERING: statLabel->setPixmap(QPixmap(":/icons/images/gear.png")); statLabel->show(); break; case LSSUB_ESTABLISHED: if (phone->has_line_media(line)) { statLabel->setPixmap(QPixmap( ":/icons/images/stat_established.png")); } else { statLabel->setPixmap(QPixmap( ":/icons/images/stat_established_nomedia.png")); } statLabel->show(); break; case LSSUB_RELEASING: statLabel->setPixmap(QPixmap(":/icons/images/gear.png")); statLabel->show(); break; default: statLabel->hide(); break; } updateLineEncryptionState(line); updateLineTimer(line); } // Update line state and enable/disable buttons depending on state void MphoneForm::updateState() { QString state; int line, other_line; bool on_hold; // indicates if a line is put on-hold bool in_conference; // indicates if a line is in a conference bool is_muted; // indicates is a line is muted t_refer_state refer_state; // indicates if a call transfer is in progress bool is_transfer_consult; // indicates if the call is a consultation bool to_be_transferred; // indicates if the line is to be transferred after consultation bool has_media; // indicates if a media stream is present t_call_info call_info; unsigned short dummy; // Update status of line 1 updateLineStatus(0); // Update status of line 2 updateLineStatus(1); // Disable/enable controls depending on the active line state t_line_substate line_substate; line = phone->get_active_line(); line_substate = phone->get_line_substate(line); on_hold = phone->is_line_on_hold(line); in_conference = phone->part_of_3way(line); is_muted = phone->is_line_muted(line); refer_state = phone->get_line_refer_state(line); is_transfer_consult = phone->is_line_transfer_consult(line, dummy); to_be_transferred = phone->line_to_be_transferred(line, dummy); has_media = phone->has_line_media(line); other_line = (line == 0 ? 1 : 0); call_info = phone->get_call_info(line); t_user *user_config = phone->get_line_user(line); // The active line may change when one of the parties in a conference // releases the call. If this happens, then update the state of the // line radio buttons. if (line == 0 && line2RadioButton->isChecked()) { line1RadioButton->setChecked(true); } else if (line == 1 && line1RadioButton->isChecked()) { line2RadioButton->setChecked(true); } // Same logic for the activate line menu items if (line == 0 && actionLine2->isChecked()) { actionLine1->setChecked(true); } else if (line == 1 && actionLine1->isChecked()) { actionLine2->setChecked(true); } updateOSD(); bool showIncomingCallPopup = false; switch(line_substate) { case LSSUB_IDLE: enableCallOptions(true); callAnswer->setEnabled(false); callBye->setEnabled(false); callReject->setEnabled(false); callRedirect->setEnabled(false); callTransfer->setEnabled(false); callHold->setEnabled(false); callConference->setEnabled(false); callMute->setEnabled(false); callDTMF->setEnabled(false); callRedial->setEnabled(ui->can_redial()); break; case LSSUB_OUTGOING_PROGRESS: enableCallOptions(false); callAnswer->setEnabled(false); callBye->setEnabled(true); callReject->setEnabled(false); callRedirect->setEnabled(false); if (is_transfer_consult && user_config->get_allow_transfer_consultation_inprog()) { callTransfer->setEnabled(true); } else { callTransfer->setEnabled(false); } callHold->setEnabled(false); callConference->setEnabled(false); callMute->setEnabled(false); callDTMF->setEnabled(call_info.dtmf_supported); callRedial->setEnabled(false); break; case LSSUB_INCOMING_PROGRESS: { enableCallOptions(false); callAnswer->setEnabled(true); callBye->setEnabled(false); callReject->setEnabled(true); callRedirect->setEnabled(true); callTransfer->setEnabled(false); callHold->setEnabled(false); callConference->setEnabled(false); callMute->setEnabled(false); callDTMF->setEnabled(call_info.dtmf_supported); callRedial->setEnabled(false); std::string name; t_call_record cr = phone->get_call_hist(line); // t_user *user_config = phone->get_line_user(line); // name = ui->format_sip_address(user_config, cr.from_display, cr.from_uri); name = cr.from_display; if (name.empty()) name = cr.from_uri.encode_no_params_hdrs(false); incomingCallPopup->setCallerName(QString::fromStdString(name)); showIncomingCallPopup = true; break; } case LSSUB_ESTABLISHED: enableCallOptions(false); callInvite->setEnabled(false); callAnswer->setEnabled(false); callBye->setEnabled(true); callReject->setEnabled(false); callRedirect->setEnabled(false); if (in_conference) { callTransfer->setEnabled(false); callHold->setEnabled(false); callConference->setEnabled(false); callDTMF->setEnabled(false); } else { callTransfer->setEnabled(has_media && call_info.refer_supported && refer_state == REFST_NULL && !to_be_transferred); callHold->setEnabled(has_media); callDTMF->setEnabled(call_info.dtmf_supported); if (phone->get_line_substate(other_line) == LSSUB_ESTABLISHED) { // If one of the lines is transferring a call, then a // conference cannot be setup. if (refer_state != REFST_NULL || phone->get_line_refer_state(other_line) != REFST_NULL) { callConference->setEnabled(false); } else { callConference->setEnabled(has_media); } } else { callConference->setEnabled(false); } } callMute->setEnabled(true); callRedial->setEnabled(false); break; case LSSUB_SEIZED: case LSSUB_ANSWERING: case LSSUB_RELEASING: // During dialing, answering and call release no other actions are // possible enableCallOptions(false); callAnswer->setEnabled(false); callBye->setEnabled(false); callReject->setEnabled(false); callRedirect->setEnabled(false); callTransfer->setEnabled(false); callHold->setEnabled(false); callConference->setEnabled(false); callMute->setEnabled(false); callDTMF->setEnabled(false); callRedial->setEnabled(false); break; default: enableCallOptions(true); callAnswer->setEnabled(true); callBye->setEnabled(true); callReject->setEnabled(true); callRedirect->setEnabled(true); callTransfer->setEnabled(true); callHold->setEnabled(true); callConference->setEnabled(false); callMute->setEnabled(true); callDTMF->setEnabled(true); callRedial->setEnabled(ui->can_redial()); } incomingCallPopup->setVisible(showIncomingCallPopup); // Set hold action in correct state callHold->setChecked(on_hold); // Set mute action in correct state callMute->setChecked(is_muted); // Set transfer action in correct state callTransfer->setChecked(is_transfer_consult); // Hide redirect form if it is still visible, but not applicable anymore if (!callRedirect->isEnabled() && redirectForm && redirectForm->isVisible()) { redirectForm->hide(); } // Hide transfer form if it is still visible, but not applicable anymore if (!callTransfer->isEnabled() && transferForm && transferForm->isVisible()) { transferForm->hide(); } // Hide DTMF form if it is still visible, but not applicable anymore if (!callDTMF->isEnabled() && dtmfForm && dtmfForm->isVisible()) { dtmfForm->hide(); } // Set last called address in the redial tool tip t_url last_url; string last_display; string last_subject; t_user *last_user; bool hide_user; if (callRedial->isEnabled() && ui->get_last_call_info(last_url, last_display, last_subject, &last_user, hide_user)) { QString s = ""; s += tr("Repeat last call"); s += "
"; s += ""; s += ""; if (!last_subject.empty()) { s.append(""; } if (hide_user) { s.append(""; } s += "
"; s += tr("User:").append(""); s += last_user->get_profile_name().c_str(); s.append("
").append(tr("Call:")).append(""); s += ui->format_sip_address(last_user, last_display, last_url).c_str(); s += "
").append(tr("Subject:")).append(""); s += last_subject.c_str(); s += "
").append(tr("Hide identity")); s += "
"; callRedial->setToolTip(s); } else { callRedial->setToolTip(tr("Repeat last call")); } callRedial->setStatusTip(tr("Repeat last call")); updateSysTrayStatus(); } // Update registration status void MphoneForm::updateRegStatus() { size_t num_registered = 0; size_t num_failed = 0; QString toolTip = ""; toolTip.append(tr("Registration status:")); toolTip.append("
"); toolTip.append(""); // Count number of successful and failed registrations. // Determine tool tip showing registration details for all users. listuser_list = phone->ref_users(); for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { toolTip.append(""); } toolTip.append("
"); toolTip.append((*i)->get_profile_name().c_str()); toolTip.append(""); if (phone->get_is_registered(*i)) { num_registered++; toolTip.append(tr("Registered")); } else if (phone->get_last_reg_failed(*i)) { num_failed++; toolTip.append(tr("Failed")); } else { toolTip.append(tr("Not registered").replace(' ', " ")); } toolTip.append("

"); toolTip.append(""); toolTip.append(tr("Click to show registrations.").replace(' ', " ")); toolTip.append(""); // Set registration status if (num_registered == user_list.size()) { // All users are registered statRegLabel->setPixmap(QPixmap(":/icons/images/twinkle16.png")); } else if (num_failed == user_list.size()) { // All users failed to register statRegLabel->setPixmap(QPixmap(":/icons/images/reg_failed.png")); } else if (num_registered > 0) { // Some users are registered statRegLabel->setPixmap(QPixmap( ":/icons/images/twinkle16.png")); } else if (num_failed > 0) { // Some users failed, none are registered statRegLabel->setPixmap(QPixmap(":/icons/images/reg_failed.png")); } else { // No users are registered, no users failed statRegLabel->setPixmap(QPixmap(":/icons/images/twinkle16-disabled.png")); } // Set tool tip with detailed info. statRegLabel->setToolTip(QString()); if (num_registered > 0 || num_failed > 0) { statRegLabel->setToolTip(toolTip); } else { statRegLabel->setToolTip(tr("No users are registered.")); } updateSysTrayStatus(); } // Create a status message based on the number of waiting messages. // On return, msg_waiting will indicate if the MWI indicator should show // waiting messages. QString MphoneForm::getMWIStatus(const t_mwi &mwi, bool &msg_waiting) const { QString status; msg_waiting = false; t_msg_summary summary = mwi.get_voice_msg_summary(); if (summary.newmsgs > 0 && summary.oldmsgs > 0) { if (summary.oldmsgs == 1) { status = tr("%1 new, 1 old message"). arg(summary.newmsgs); } else { status = tr("%1 new, %2 old messages"). arg(summary.newmsgs). arg(summary.oldmsgs); } msg_waiting = true; } else if (summary.newmsgs > 0 && summary.oldmsgs == 0) { if (summary.newmsgs == 1) { status = tr("1 new message"); } else { status = tr("%1 new messages"). arg(summary.newmsgs); } msg_waiting = true; } else if (summary.oldmsgs > 0) { if (summary.oldmsgs == 1) { status = tr("1 old message"); } else { status = tr("%1 old messages"). arg(summary.oldmsgs); } } else { if (mwi.get_msg_waiting()) { status = tr("Messages waiting"); msg_waiting = true; } else { status = tr("No messages"); } } return status.replace(' ', " "); } // Flash the MWI icon void MphoneForm::flashMWI() { if (mwiFlashStatus) { mwiFlashStatus = false; statMWILabel->setPixmap(QPixmap( ":/icons/images/mwi_none16.png")); } else { mwiFlashStatus = true; statMWILabel->setPixmap(QPixmap( ":/icons/images/mwi_new16.png")); } } // Update MWI void MphoneForm::updateMwi() { bool mwi_known = false; bool mwi_new_msgs = false; bool mwi_failure = false; // Determine tool tip QString toolTip = tr("Voice mail status:").append("\n"); toolTip.append("
"); listuser_list = phone->ref_users(); for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { toolTip.append(""); } toolTip.append("
"); toolTip.append((*i)->get_profile_name().c_str()); t_mwi mwi = phone->get_mwi(*i); toolTip.append(""); if (phone->is_mwi_subscribed(*i)) { if (mwi.get_status() == t_mwi::MWI_KNOWN) { bool new_msgs; QString status = getMWIStatus(mwi, new_msgs); toolTip.append(status); mwi_known = true; mwi_new_msgs |= new_msgs; } else if (mwi.get_status() == t_mwi::MWI_FAILED) { toolTip.append(tr("Failure")); mwi_failure = true; } else { toolTip.append(tr("Unknown")); } } else { if ((*i)->get_mwi_sollicited()) { if (mwi.get_status() == t_mwi::MWI_FAILED) { toolTip.append(tr("Failure")); mwi_failure = true; } else { toolTip.append(tr("Unknown")); } } else { // Unsollicited MWI if (mwi.get_status() == t_mwi::MWI_KNOWN) { bool new_msgs; QString status = getMWIStatus(mwi, new_msgs); toolTip.append(status); mwi_known = true; mwi_new_msgs |= new_msgs; } else { toolTip.append(tr("Unknown")); } } } toolTip.append("

"); toolTip.append(""); toolTip.append(tr("Click to access voice mail.").replace(' ', " ")); toolTip.append(""); // Set MWI icon if (mwi_new_msgs) { statMWILabel->setPixmap(QPixmap( ":/icons/images/mwi_new16.png")); mwiFlashStatus = true; // Start the flash MWI timer to flash the indicator tmrFlashMWI.start(1000); } else if (mwi_failure) { tmrFlashMWI.stop(); statMWILabel->setPixmap(QPixmap( ":/icons/images/mwi_failure16.png")); } else if (mwi_known) { tmrFlashMWI.stop(); statMWILabel->setPixmap(QPixmap( ":/icons/images/mwi_none16.png")); } else { tmrFlashMWI.stop(); statMWILabel->setPixmap(QPixmap( ":/icons/images/mwi_none16_dis.png")); } // Set tool tip statMWILabel->setToolTip(toolTip); updateSysTrayStatus(); } // Update active services status void MphoneForm::updateServicesStatus() { size_t num_dnd = 0; size_t num_cf = 0; size_t num_auto_answer = 0; QString tipDnd = ""; tipDnd += tr("Do not disturb active for:").replace(' ', " "); tipDnd += "
\n"; QString tipCf = ""; tipCf += tr("Redirection active for:").replace(' ', " "); tipCf += "
\n
"; QString tipAa = ""; tipAa += tr("Auto answer active for:").replace(' ', " "); tipAa += "
\n
"; // Calculate number of services active. // Determine tool tips with detailed service status for all users. listuser_list = phone->ref_users(); for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { if (phone->ref_service(*i)->is_dnd_active()) { num_dnd++; tipDnd.append(""); } if (phone->ref_service(*i)->is_cf_active()) { num_cf++; tipCf.append(""); } if (phone->ref_service(*i)->is_auto_answer_active()) { num_auto_answer++; tipAa.append(""); } } QString footer = ""; footer += tr("Click to activate/deactivate").replace(' ', " "); footer += ""; tipDnd.append("
"); tipDnd.append((*i)->get_profile_name().c_str()); tipDnd.append("
"); tipCf.append((*i)->get_profile_name().c_str()); tipCf.append("
"); tipAa.append((*i)->get_profile_name().c_str()); tipAa.append("

"); tipDnd.append(footer); tipCf.append("
"); tipCf.append(footer); tipAa.append("
"); tipAa.append(footer); // Set service status if (num_dnd == user_list.size()) { // All users enabled dnd statDndLabel->setPixmap(QPixmap(":/icons/images/cancel.png")); } else if (num_dnd > 0) { // Some users enabled dnd statDndLabel->setPixmap(QPixmap(":/icons/images/cancel.png")); } else { // No users enabeld dnd statDndLabel->setPixmap(QPixmap(":/icons/images/cancel-disabled.png")); } if (num_cf == user_list.size()) { // All users enabled redirecton statCfLabel->setPixmap(QPixmap(":/icons/images/cf.png")); } else if (num_cf > 0) { // Some users enabled redirection statCfLabel->setPixmap(QPixmap(":/icons/images/cf.png")); } else { // No users enabled redirection statCfLabel->setPixmap(QPixmap(":/icons/images/cf-disabled.png")); } if (num_auto_answer == user_list.size()) { // All users enabled auto answer statAaLabel->setPixmap(QPixmap(":/icons/images/auto_answer.png")); } else if (num_auto_answer > 0) { // Some users enabled auto answer statAaLabel->setPixmap(QPixmap( ":/icons/images/auto_answer.png")); } else { // No users enabeld auto answer statAaLabel->setPixmap(QPixmap( ":/icons/images/auto_answer-disabled.png")); } // Set tool tip with detailed info for multiple users. statDndLabel->setToolTip(QString()); statCfLabel->setToolTip(QString()); statAaLabel->setToolTip(QString()); QString clickToActivate(""); clickToActivate += tr("Click to activate").replace(' ', " "); clickToActivate += ""; if (num_dnd > 0) { statDndLabel->setToolTip(tipDnd); } else { QString status("

"); status += tr("Do not disturb is not active.").replace(' ', " "); status += "

"; status += clickToActivate; statDndLabel->setToolTip(status); } if (num_cf > 0) { statCfLabel->setToolTip(tipCf); } else { QString status("

"); status += tr("Redirection is not active.").replace(' ', " "); status += "

"; status += clickToActivate; statCfLabel->setToolTip(status); } if (num_auto_answer > 0) { statAaLabel->setToolTip(tipAa); } else { QString status("

"); status += tr("Auto answer is not active.").replace(' ', " "); status += "

"; status += clickToActivate; statAaLabel->setToolTip(status); } updateSysTrayStatus(); } void MphoneForm::updateMissedCallStatus(int num_missed_calls) { QString clickDetails(""); clickDetails += tr("Click to see call history for details.").replace(' ', " "); clickDetails += ""; if (num_missed_calls == 0) { statMissedLabel->setPixmap(QPixmap(":/icons/images/missed-disabled.png")); QString status("

"); status += tr("You have no missed calls.").replace(' ', " "); status += "

"; status += clickDetails; statMissedLabel->setToolTip(status); } else { statMissedLabel->setPixmap( QPixmap(":/icons/images/missed.png")); QString tip("

"); if (num_missed_calls == 1) { tip += tr("You missed 1 call.").replace(' ', " "); } else { tip += tr("You missed %1 calls.").arg(num_missed_calls). replace(' ', " "); } tip += "

"; tip += clickDetails; statMissedLabel->setToolTip(tip); } updateSysTrayStatus(); } // Update system tray status void MphoneForm::updateSysTrayStatus() { QString icon_name; bool cf_active = false; bool dnd_active = false; bool auto_answer_active = false; bool multi_services = false; int num_services; bool msg_waiting = false; if (!sysTray) return; // Get status of active line int line = phone->get_active_line(); t_line_substate line_substate = phone->get_line_substate(line); list user_list = phone->ref_users(); switch(line_substate) { case LSSUB_IDLE: case LSSUB_SEIZED: // Determine MWI and service status user_list = phone->ref_users(); for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { t_mwi mwi = phone->get_mwi(*i); if (mwi.get_status() == t_mwi::MWI_KNOWN && mwi.get_msg_waiting() && mwi.get_voice_msg_summary().newmsgs > 0) { msg_waiting = true; } else if (phone->ref_service(*i)->multiple_services_active()) { multi_services = true; } else { if (phone->ref_service(*i)->is_dnd_active()) { dnd_active = true; } if (phone->ref_service(*i)->is_cf_active()) { cf_active = true; } if (phone->ref_service(*i)->is_auto_answer_active()) { auto_answer_active = true; } } } // If there are messages waiting, then show MWI icon if (msg_waiting) { icon_name = "sys_mwi"; break; } // If there are missed calls, then show the missed call icon if (call_history->get_num_missed_calls() > 0) { icon_name = "sys_missed"; break; } // If a service is active, then show the service icon num_services = (dnd_active ? 1 : 0) + (cf_active ? 1 : 0) + (auto_answer_active ? 1 : 0); if (multi_services || num_services > 1) { icon_name = "sys_services"; } else if (dnd_active) { icon_name = "sys_dnd"; } else if (cf_active) { icon_name = "sys_redir"; } else if (auto_answer_active) { icon_name = "sys_auto_ans"; } else { // No service is active, show the idle icon if (icon_name.isEmpty()) icon_name = "sys_idle"; } break; case LSSUB_ESTABLISHED: if (phone->is_line_on_hold(line)) { icon_name = "sys_hold"; } else if (phone->is_line_muted(line)) { icon_name = "sys_mute"; } else if (phone->is_line_encrypted(line)) { t_audio_session *as = phone->get_line(line)->get_audio_session(); if (as && as->get_zrtp_sas_confirmed()) { icon_name = "sys_encrypted_verified"; } else { icon_name = "sys_encrypted"; } } else { icon_name = "sys_busy_estab"; } break; default: // Line is in a busy transient state icon_name = "sys_busy_trans"; } // Based on the registration status use the active or disabled version // of the icon. bool registered = false; for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { if (phone->get_is_registered(*i)) { registered = true; break; } } if (registered) { icon_name += ".png"; } else { icon_name += "_dis.png"; } sysTray->setIcon(QPixmap(":/icons/images/" + icon_name)); } // Update menu status based on the number of active users void MphoneForm::updateMenuStatus() { // Some menu options should be toggle actions when there is only // 1 user active, but they should be normal actions when there are // multiple users. disconnect(serviceDnd, 0, 0, 0); disconnect(serviceAutoAnswer, 0, 0, 0); if (phone->ref_users().size() == 1) { t_service *srv = phone->ref_service(phone->ref_users().front()); serviceDnd->setCheckable(true); serviceDnd->setChecked(srv->is_dnd_active()); connect(serviceDnd, SIGNAL(toggled(bool)), this, SLOT(srvDnd(bool))); serviceAutoAnswer->setCheckable(true); serviceAutoAnswer->setChecked(srv->is_auto_answer_active()); connect(serviceAutoAnswer, SIGNAL(toggled(bool)), this, SLOT(srvAutoAnswer(bool))); } else { serviceDnd->setChecked(false); serviceDnd->setCheckable(false); connect(serviceDnd, SIGNAL(triggered()), this, SLOT(srvDnd())); serviceAutoAnswer->setChecked(false); serviceAutoAnswer->setCheckable(false); connect(serviceAutoAnswer, SIGNAL(triggered()), this, SLOT(srvAutoAnswer())); } #ifdef WITH_DIAMONDCARD updateDiamondcardMenu(); #endif } void MphoneForm::updateDiamondcardMenu() { // If one Diamondcard user is active, then create actions in the Diamondcard // main menu for recharging, call history, etc. These actions will show the // Diamondcard web page. // If multiple Diamondcard users are active then create a submenu of each // Diamondcard action. In each submenu create an item for each user. // When a user item is clicked, the web page for the action and that user is // shown. list diamondcard_users = diamondcard_get_users(phone); // Menu item identifiers static QAction* rechargeId = nullptr; static QAction* balanceHistoryId = nullptr; static QAction* callHistoryId = nullptr; static QAction* adminCenterId = nullptr; // Sub menu's static QMenu *rechargeMenu = NULL; static QMenu *balanceHistoryMenu = NULL; static QMenu *callHistoryMenu = NULL; static QMenu *adminCenterMenu = NULL; // Clear old menu removeDiamondcardAction(rechargeId); removeDiamondcardAction(balanceHistoryId); removeDiamondcardAction(callHistoryId); removeDiamondcardAction(adminCenterId); removeDiamondcardMenu(rechargeMenu); removeDiamondcardMenu(balanceHistoryMenu); removeDiamondcardMenu(callHistoryMenu); removeDiamondcardMenu(adminCenterMenu); if (diamondcard_users.size() <= 1) { rechargeId = Diamondcard->addAction(tr("Recharge..."), this, SLOT(DiamondcardRecharge())); rechargeId->setData(0); balanceHistoryId = Diamondcard->addAction(tr("Balance history..."), this, SLOT(DiamondcardBalanceHistory())); balanceHistoryId->setData(0); callHistoryId = Diamondcard->addAction(tr("Call history..."), this, SLOT(DiamondcardCallHistory())); callHistoryId->setData(0); adminCenterId = Diamondcard->addAction(tr("Admin center..."), this, SLOT(DiamondcardAdminCenter())); adminCenterId->setData(0); // Disable actions as there is no active Diamondcard users. if (diamondcard_users.empty()) { rechargeId->setEnabled(false); balanceHistoryId->setEnabled(false); callHistoryId->setEnabled(false); adminCenterId->setEnabled(false); } } else { // Add the Diamondcard popup menus to the main Diamondcard menu. rechargeMenu = Diamondcard->addMenu(tr("Recharge")); balanceHistoryMenu = Diamondcard->addMenu(tr("Balance history")); callHistoryMenu = Diamondcard->addMenu(tr("Call history")); adminCenterMenu = Diamondcard->addMenu(tr("Admin center")); // No MEMMAN registration as the popup menu may be automatically // deleted by Qt on application close down. This would show up as // a memory leak in MEMMAN. // Insert a menu item for each Diamondcard user. int idx = 0; for (list::const_iterator it = diamondcard_users.begin(); it != diamondcard_users.end(); ++it) { QAction* menuId; t_user *user = *it; // Set the index in the user list as parameter to the menu item. // When the menu item gets clicked, then the receiver of the signal // received this parameter and can use it as an index in the user list // to find the user. menuId = rechargeMenu->addAction(user->get_profile_name().c_str(), this, SLOT(DiamondcardRecharge())); menuId->setData(idx); menuId = balanceHistoryMenu->addAction(user->get_profile_name().c_str(), this, SLOT(DiamondcardBalanceHistory())); menuId->setData(idx); menuId = callHistoryMenu->addAction(user->get_profile_name().c_str(), this, SLOT(DiamondcardCallHistory())); menuId->setData(idx); menuId = adminCenterMenu->addAction(user->get_profile_name().c_str(), this, SLOT(DiamondcardAdminCenter())); menuId->setData(idx); ++idx; } } } void MphoneForm::removeDiamondcardAction(QAction*& act) { if (act != nullptr) { Diamondcard->removeAction(act); act = nullptr; } } void MphoneForm::removeDiamondcardMenu(QMenu* &menu) { if (menu) { delete menu; menu = NULL; } } void MphoneForm::phoneRegister() { t_gui *gui = (t_gui *)ui; list user_list = phone->ref_users(); if (user_list.size() > 1) { if (selectUserForm) { MEMMAN_DELETE(selectUserForm); delete (selectUserForm); } selectUserForm = new SelectUserForm(this); selectUserForm->setModal(true); MEMMAN_NEW(selectUserForm); connect(selectUserForm, SIGNAL(selection(list)), this, SLOT(do_phoneRegister(list))); selectUserForm->show(SELECT_REGISTER); } else { gui->action_register(user_list); } } void MphoneForm::do_phoneRegister(list user_list) { ((t_gui *)ui)->action_register(user_list); } void MphoneForm::phoneDeregister() { t_gui *gui = (t_gui *)ui; list user_list = phone->ref_users(); if (user_list.size() > 1) { if (selectUserForm) { MEMMAN_DELETE(selectUserForm); delete (selectUserForm); } selectUserForm = new SelectUserForm(this); selectUserForm->setModal(true); MEMMAN_NEW(selectUserForm); connect(selectUserForm, SIGNAL(selection(list)), this, SLOT(do_phoneDeregister(list))); selectUserForm->show(SELECT_DEREGISTER); } else { gui->action_deregister(user_list, false); } } void MphoneForm::do_phoneDeregister(list user_list) { ((t_gui *)ui)->action_deregister(user_list, false); } void MphoneForm::phoneDeregisterAll() { t_gui *gui = (t_gui *)ui; list user_list = phone->ref_users(); if (user_list.size() > 1) { if (selectUserForm) { MEMMAN_DELETE(selectUserForm); delete (selectUserForm); } selectUserForm = new SelectUserForm(this); selectUserForm->setModal(true); MEMMAN_NEW(selectUserForm); connect(selectUserForm, SIGNAL(selection(list)), this, SLOT(do_phoneDeregisterAll(list))); selectUserForm->show(SELECT_DEREGISTER_ALL); } else { gui->action_deregister(user_list, true); } } void MphoneForm::do_phoneDeregisterAll(list user_list) { ((t_gui *)ui)->action_deregister(user_list, true); } void MphoneForm::phoneShowRegistrations() { list user_list = phone->ref_users(); ((t_gui *)ui)->action_show_registrations(user_list); } // Show the semi-modal invite window void MphoneForm::phoneInvite(t_user * user_config, const QString &dest, const QString &subject, bool anonymous) { // Seize the line, so no incoming call can take the line if (!((t_gui *)ui)->action_seize()) return; if (inviteForm) { inviteForm->clear(); } else { inviteForm = new InviteForm(this); inviteForm->setModal(true); MEMMAN_NEW(inviteForm); // Initialize the destination history list for (int i = callComboBox->count() - 1; i >= 0; i--) { inviteForm->addToInviteComboBox(callComboBox->itemText(i)); } connect(inviteForm, SIGNAL(destination(t_user *, const QString &, const t_url &, const QString &, bool)), this, SLOT(do_phoneInvite(t_user *, const QString &, const t_url &, const QString &, bool))); connect(inviteForm, SIGNAL(raw_destination(const QString &)), this, SLOT(addToCallComboBox(const QString &))); } inviteForm->show(user_config, dest, subject, anonymous); updateState(); } void MphoneForm::phoneInvite(const QString &dest, const QString &subject, bool anonymous) { t_user *user = phone->ref_user_profile(userComboBox->currentText().toStdString()); if (!user) { log_file->write_report("Cannot find user profile.", "MphoneForm::phoneInvite", LOG_NORMAL, LOG_CRITICAL); return; } phoneInvite(user, dest, subject, anonymous); } void MphoneForm::phoneInvite() { t_user *user = phone->ref_user_profile(userComboBox->currentText().toStdString()); if (!user) { log_file->write_report("Cannot find user profile.", "MphoneForm::phoneInvite", LOG_NORMAL, LOG_CRITICAL); return; } phoneInvite(user, "", "", false); } // Execute the invite action. This slot is connected to the destination // signal of the invite window. void MphoneForm::do_phoneInvite(t_user *user_config, const QString &display, const t_url &destination, const QString &subject, bool anonymous) { ((t_gui *)ui)->action_invite(user_config, destination, display.toStdString(), subject.toStdString(), anonymous); updateState(); } // Redial last call void MphoneForm::phoneRedial(void) { t_url url; string display, subject; t_user *user_config; bool hide_user; if (!ui->get_last_call_info(url, display, subject, &user_config, hide_user)) return; ((t_gui *)ui)->action_invite(user_config, url, display, subject, hide_user); updateState(); } void MphoneForm::phoneAnswer() { ((t_gui *)ui)->action_answer(); updateState(); } // A call can be answered from the systray popup. The user may have // switched lines, the systray popup answer button should answer the // correct line. void MphoneForm::phoneAnswerFromSystrayPopup() { #ifdef HAVE_KDE unsigned short line = ((t_gui *)ui)->get_line_sys_tray_popup(); unsigned short active_line = phone->get_active_line(); if (line != active_line) { ((t_gui *)ui)->action_activate_line(line); } ((t_gui *)ui)->action_answer(); updateState(); #endif } void MphoneForm::phoneBye() { ((t_gui *)ui)->action_bye(); updateState(); } void MphoneForm::phoneReject() { ((t_gui *)ui)->action_reject(); updateState(); } // A call can be rejected from the systray popup. The user may have // switched lines, the systray popup reject button should answer the // correct line. void MphoneForm::phoneRejectFromSystrayPopup() { #ifdef HAVE_KDE unsigned short line = ((t_gui *)ui)->get_line_sys_tray_popup(); ((t_gui *)ui)->action_reject(line); updateState(); #endif } // Show the semi-modal redirect form void MphoneForm::phoneRedirect(const list &contacts) { int active_line = phone->get_active_line(); t_user *user_config = phone->get_line_user(active_line); if (redirectForm) { MEMMAN_DELETE(redirectForm); delete (redirectForm); } redirectForm = new RedirectForm(this); redirectForm->setModal(true); MEMMAN_NEW(redirectForm); connect(redirectForm, SIGNAL(destinations(const list &)), this, SLOT(do_phoneRedirect(const list &))); redirectForm->show(user_config, contacts); } void MphoneForm::phoneRedirect() { const list l; phoneRedirect(l); } // Execute the redirect action. void MphoneForm::do_phoneRedirect(const list &destinations) { ((t_gui *)ui)->action_redirect(destinations); updateState(); } // Show the semi-modal call transfer window void MphoneForm::phoneTransfer(const string &dest, t_transfer_type transfer_type) { int active_line = phone->get_active_line(); t_user *user_config = phone->get_line_user(active_line); // Hold the call if setting in user profile indicates call hold if (user_config->get_referrer_hold()) { phoneHold(true); } if (transferForm) { MEMMAN_DELETE(transferForm); delete transferForm; } transferForm = new TransferForm(this); transferForm->setModal(true); MEMMAN_NEW(transferForm); connect(transferForm, SIGNAL(destination(const t_display_url &, t_transfer_type)), this, SLOT(do_phoneTransfer(const t_display_url &, t_transfer_type))); if (dest.empty() && transfer_type == TRANSFER_BASIC) { // Let form pick a default transfer type based on the current // call status. transferForm->show(user_config); } else { // Set passed destination and transfer type in form transferForm->show(user_config, dest, transfer_type); } updateState(); } void MphoneForm::phoneTransfer() { unsigned short active_line = phone->get_active_line(); unsigned short dummy; if (phone->is_line_transfer_consult(active_line, dummy)) { do_phoneTransferLine(); } else { phoneTransfer("", TRANSFER_BASIC); } } // Execute the transfer action. This slot is connected to the destination // signal of the transfer window. void MphoneForm::do_phoneTransfer(const t_display_url &destination, t_transfer_type transfer_type) { unsigned short active_line; unsigned short other_line; switch (transfer_type) { case TRANSFER_BASIC: ((t_gui *)ui)->action_refer(destination.url, destination.display); break; case TRANSFER_CONSULT: ((t_gui *)ui)->action_setup_consultation_call( destination.url, destination.display); break; case TRANSFER_OTHER_LINE: active_line = phone->get_active_line(); other_line = (active_line == 0 ? 1 : 0); if (phone->get_line_substate(other_line) == LSSUB_ESTABLISHED) { ((t_gui *)ui)->action_refer(active_line, other_line); } else { // The other line was released while the user was entering // the refer-target. t_user *user_config = phone->get_line_user(active_line); if (user_config->get_referrer_hold()) { phoneHold(false); } } break; default: assert(false); } updateState(); } // Transfer the remote party on the held line to the remote party on the // active line. void MphoneForm::do_phoneTransferLine() { unsigned short active_line = phone->get_active_line(); unsigned short line_to_be_transferred; if (!phone->is_line_transfer_consult(active_line, line_to_be_transferred)) { // Somehow the line is not a consultation call. updateState(); return; } ((t_gui *)ui)->action_refer(line_to_be_transferred, active_line); updateState(); } void MphoneForm::phoneHold(bool on) { if (on) { ((t_gui *)ui)->action_hold(); } else { ((t_gui *)ui)->action_retrieve(); } updateState(); } void MphoneForm::phoneConference() { ((t_gui *)ui)->action_conference(); updateState(); } void MphoneForm::phoneMute(bool on) { ((t_gui *)ui)->action_mute(on); updateState(); } void MphoneForm::phoneTermCap(const QString &dest) { // In-dialog OPTIONS request int line = phone->get_active_line(); if (phone->get_line_substate(line) == LSSUB_ESTABLISHED) { ((t_gui *)ui)->action_options(); return; } // Out-of-dialog OPTIONS request if (termCapForm) { MEMMAN_DELETE(termCapForm); delete (termCapForm); } termCapForm = new TermCapForm(this); termCapForm->setModal(true); MEMMAN_NEW(termCapForm); connect(termCapForm, SIGNAL(destination(t_user *, const t_url &)), this, SLOT(do_phoneTermCap(t_user *, const t_url &))); t_user *user = phone->ref_user_profile(userComboBox->currentText().toStdString()); if (!user) { log_file->write_report("Cannot find user profile.", "MphoneForm::phoneTermcap", LOG_NORMAL, LOG_CRITICAL); return; } termCapForm->show(user, dest); } void MphoneForm::phoneTermCap() { phoneTermCap(""); } void MphoneForm::do_phoneTermCap(t_user *user_config, const t_url &destination) { ((t_gui *)ui)->action_options(user_config, destination); } void MphoneForm::phoneDTMF() { if (!dtmfForm) { dtmfForm = new DtmfForm(this); MEMMAN_NEW(dtmfForm); connect(dtmfForm, SIGNAL(digits(const QString &)), this, SLOT(sendDTMF(const QString &))); } dtmfForm->show(); } void MphoneForm::sendDTMF(const QString &digits) { ((t_gui *)ui)->action_dtmf(digits.toStdString()); } void MphoneForm::startMessageSession(void) { t_user *user = phone->ref_user_profile(userComboBox->currentText().toStdString()); if (!user) { log_file->write_report("Cannot find user profile.", "MphoneForm::startMessageSession", LOG_NORMAL, LOG_CRITICAL); return; } im::t_msg_session *session = new im::t_msg_session(user); MEMMAN_NEW(session); ((t_gui *)ui)->addMessageSession(session); MessageFormView *messageFormView = new MessageFormView(NULL, session); MEMMAN_NEW(messageFormView); messageFormView->show(); } void MphoneForm::startMessageSession(t_buddy *buddy) { t_user *user_config = buddy->get_user_profile(); t_url dest_url(ui->expand_destination(user_config, buddy->get_sip_address())); if (!dest_url.is_valid()) return; string display = buddy->get_name(); // Find an existing session im::t_msg_session *session = ((t_gui *)ui)->getMessageSession(user_config, dest_url, display); if (!session) { // There is no session yet, create one. session = new im::t_msg_session(user_config, t_display_url(dest_url, display)); MEMMAN_NEW(session); ((t_gui *)ui)->addMessageSession(session); MessageFormView *view = new MessageFormView(NULL, session); MEMMAN_NEW(view); view->show(); } } void MphoneForm::phoneConfirmZrtpSas(int line) { ((t_gui *)ui)->action_confirm_zrtp_sas(line); updateState(); } void MphoneForm::phoneConfirmZrtpSas() { ((t_gui *)ui)->action_confirm_zrtp_sas(); updateState(); } void MphoneForm::phoneResetZrtpSasConfirmation(int line) { ((t_gui *)ui)->action_reset_zrtp_sas_confirmation(line); updateState(); } void MphoneForm::phoneResetZrtpSasConfirmation() { ((t_gui *)ui)->action_reset_zrtp_sas_confirmation(); updateState(); } void MphoneForm::phoneEnableZrtp(bool on) { if (on) { ((t_gui *)ui)->action_enable_zrtp(); } else { ((t_gui *)ui)->action_zrtp_request_go_clear(); } updateState(); } void MphoneForm::phoneZrtpGoClearOk(unsigned short line) { ((t_gui *)ui)->action_zrtp_go_clear_ok(line); updateState(); } // Radio button for line 1 changed state void MphoneForm::line1rbChangedState( bool on ) { // If the radio button is switched off, then return, the toggle // on the other line will handle the action if (!on) return; ((t_gui *)ui)->action_activate_line(0); } void MphoneForm::line2rbChangedState( bool on ) { // If the radio button is switched off, then return, the toggle // on the other line will handle the action if (!on) return; ((t_gui *)ui)->action_activate_line(1); } void MphoneForm::actionLine1Toggled( bool on) { if (!on) return; ((t_gui *)ui)->action_activate_line(0); } void MphoneForm::actionLine2Toggled( bool on) { if (!on) return; ((t_gui *)ui)->action_activate_line(1); } // Enable/disable dnd when there is 1 user active void MphoneForm::srvDnd( bool on ) { ((t_gui *)ui)->srv_dnd(phone->ref_users(), on); updateServicesStatus(); } // Enable/disable dnd when there are multiple users active void MphoneForm::srvDnd() { if (selectUserForm) { MEMMAN_DELETE(selectUserForm); delete (selectUserForm); } selectUserForm = new SelectUserForm(this); selectUserForm->setModal(true); MEMMAN_NEW(selectUserForm); connect(selectUserForm, SIGNAL(selection(list)), this, SLOT(do_srvDnd_enable(list))); connect(selectUserForm, SIGNAL(not_selected(list)), this, SLOT(do_srvDnd_disable(list))); selectUserForm->show(SELECT_DND); } void MphoneForm::do_srvDnd_enable(list user_list) { ((t_gui *)ui)->srv_dnd(user_list, true); updateServicesStatus(); } void MphoneForm::do_srvDnd_disable(list user_list) { ((t_gui *)ui)->srv_dnd(user_list, false); updateServicesStatus(); } // Enable/disable auto answer when there is 1 user active void MphoneForm::srvAutoAnswer( bool on ) { ((t_gui *)ui)->srv_auto_answer(phone->ref_users(), on); updateServicesStatus(); } // Enable/disable auto answer when there are multiple users active void MphoneForm::srvAutoAnswer() { if (selectUserForm) { MEMMAN_DELETE(selectUserForm); delete (selectUserForm); } selectUserForm = new SelectUserForm(this); selectUserForm->setModal(true); MEMMAN_NEW(selectUserForm); connect(selectUserForm, SIGNAL(selection(list)), this, SLOT(do_srvAutoAnswer_enable(list))); connect(selectUserForm, SIGNAL(not_selected(list)), this, SLOT(do_srvAutoAnswer_disable(list))); selectUserForm->show(SELECT_AUTO_ANSWER); } void MphoneForm::do_srvAutoAnswer_enable(list user_list) { ((t_gui *)ui)->srv_auto_answer(user_list, true); updateServicesStatus(); } void MphoneForm::do_srvAutoAnswer_disable(list user_list) { ((t_gui *)ui)->srv_auto_answer(user_list, false); updateServicesStatus(); } void MphoneForm::srvRedirect() { if (!srvRedirectForm) { srvRedirectForm = new SrvRedirectForm(this); srvRedirectForm->setModal(true); MEMMAN_NEW(srvRedirectForm); connect(srvRedirectForm, SIGNAL(destinations(t_user *, const list &, const list &, const list &)), this, SLOT(do_srvRedirect(t_user *, const list &, const list &, const list &))); } srvRedirectForm->show(); } void MphoneForm::do_srvRedirect(t_user *user_config, const list &always, const list &busy, const list &noanswer) { // Redirection always if (always.empty()) { ((t_gui *)ui)->srv_disable_cf(user_config, CF_ALWAYS); } else { ((t_gui *)ui)->srv_enable_cf(user_config, CF_ALWAYS, always); } // Redirection busy if (busy.empty()) { ((t_gui *)ui)->srv_disable_cf(user_config, CF_BUSY); } else { ((t_gui *)ui)->srv_enable_cf(user_config, CF_BUSY, busy); } // Redirection no answer if (noanswer.empty()) { ((t_gui *)ui)->srv_disable_cf(user_config, CF_NOANSWER); } else { ((t_gui *)ui)->srv_enable_cf(user_config, CF_NOANSWER, noanswer); } updateServicesStatus(); } void MphoneForm::about() { QString s = sys_config->about(true).c_str(); QMessageBox mbAbout(PRODUCT_NAME, s.replace(' ', " "), QMessageBox::Information, QMessageBox::Ok | QMessageBox::Default, QMessageBox::NoButton, QMessageBox::NoButton); mbAbout.setIconPixmap(QPixmap(":/icons/images/twinkle48.png")); mbAbout.exec(); } void MphoneForm::aboutQt() { QMessageBox::aboutQt(this, PRODUCT_NAME); } void MphoneForm::manual() { ((t_gui *)ui)->open_url_in_browser("http://www.twinklephone.com"); } void MphoneForm::editUserProfile() { if (!userProfileForm) { userProfileForm = new UserProfileForm(this); userProfileForm->setModal(true); MEMMAN_NEW(userProfileForm); connect(userProfileForm, SIGNAL(authCredentialsChanged(t_user *, const string&)), this, SLOT(updateAuthCache(t_user *, const string&))); connect(userProfileForm, SIGNAL(stunServerChanged(t_user *)), this, SLOT(updateStunSettings(t_user *))); // MWI settings change triggers an unsubscribe connect(userProfileForm, SIGNAL(mwiChangeUnsubscribe(t_user *)), this, SLOT(unsubscribeMWI(t_user *))); // MWI settings change triggers a subscribe connect(userProfileForm, SIGNAL(mwiChangeSubscribe(t_user *)), this, SLOT(subscribeMWI(t_user *))); } userProfileForm->show(phone->ref_users(), userComboBox->currentText()); } void MphoneForm::editSysSettings() { if (!sysSettingsForm) { sysSettingsForm = new SysSettingsForm(this); sysSettingsForm->setModal(true); MEMMAN_NEW(sysSettingsForm); connect(sysSettingsForm, SIGNAL(sipUdpPortChanged()), this, SLOT(updateSipUdpPort())); connect(sysSettingsForm, SIGNAL(rtpPortChanged()), this, SLOT(updateRtpPorts())); } sysSettingsForm->show(); } void MphoneForm::selectProfile() { if (!selectProfileForm) { selectProfileForm = new SelectProfileForm(this); selectProfileForm->setModal(true); MEMMAN_NEW(selectProfileForm); connect(selectProfileForm, SIGNAL(selection(const list &)), this, SLOT(newUsers(const list &))); connect(selectProfileForm, SIGNAL(profileRenamed()), this, SLOT(updateUserComboBox())); connect(selectProfileForm, SIGNAL(profileRenamed()), this, SLOT(populateBuddyList())); } selectProfileForm->showForm(this); } // A new set of users has been selected. // Remove users from the current user set that are not in the selection. // Add users from the selection that are not in the current set of users. void MphoneForm::newUsers(const list &profiles) { string error_msg; // NOTE: First users must be removed. It could be that a // user profile of an active was renamed. In this case, the user // with the old profile name is first removed and then added again. list user_list = phone->ref_users(); // Remove current users that are not selected anymore. for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { if (std::find(profiles.begin(), profiles.end(), (*i)->get_filename().c_str()) == profiles.end()) { // User is not selected anymore. // Unsubscribe MWI if (phone->is_mwi_subscribed(*i)) { phone->pub_unsubscribe_mwi(*i); } // Unpublish presence of user phone->pub_unpublish_presence(*i); // Unsubscribe presence phone->pub_unsubscribe_presence(*i); // Deregister user if (phone->get_is_registered(*i)) { phone->pub_registration(*i, REG_DEREGISTER); } log_file->write_header("MphoneForm::newUsers"); log_file->write_raw("Stop user profile: "); log_file->write_raw((*i)->get_profile_name()); log_file->write_endl(); log_file->write_footer(); phone->remove_phone_user(*(*i)); } } // Determine which users to add list add_profile_list; for (list::const_iterator i = profiles.begin(); i != profiles.end(); i++) { QString profile = (*i).c_str(); // Strip off the .cfg extension profile.truncate(profile.length() - 4); if (!phone->ref_user_profile(profile.toStdString())) { add_profile_list.push_back(*i); } } // Add new phone users QProgressDialog progress(tr("Starting user profiles..."), "Abort", 0, add_profile_list.size(), this); progress.setModal(true); progress.setWindowTitle(PRODUCT_NAME); progress.setMinimumDuration(200); int progressStep = 0; for (list::iterator i = add_profile_list.begin(); i != add_profile_list.end(); i++) { progress.setValue(progressStep); qApp->processEvents(); if (progress.wasCanceled()) { log_file->write_report("User aborted startup of new users.", "MphoneForm::newUsers"); break; } t_user user_config; // Read user configuration if (user_config.read_config(*i, error_msg)) { t_user *dup_user; log_file->write_header("MphoneForm::newUsers"); log_file->write_raw("Run user profile: "); log_file->write_raw(user_config.get_profile_name()); log_file->write_endl(); log_file->write_footer(); if (phone->add_phone_user(user_config, &dup_user)) { // NAT discovery if (!phone->stun_discover_nat(&user_config, error_msg)) { // Warn user that the STUN settings will not work. ((t_gui *)ui)->cb_show_msg(this, error_msg, MSG_WARNING); } // Register at startup if (user_config.get_register_at_startup()) { phone->pub_registration(&user_config, REG_REGISTER, DUR_REGISTRATION(&user_config)); } else { // No registration needed, initialize extensions now. phone->init_extensions(&user_config); } // Extension initialization will be done after // registration succeeded. } else { error_msg = tr("The following profiles are both for user %1").arg(QString::fromStdString(user_config.get_name())).toStdString(); error_msg += '@'; error_msg += user_config.get_domain(); error_msg += ":\n\n"; error_msg += user_config.get_profile_name(); error_msg += "\n"; error_msg += dup_user->get_profile_name(); error_msg += "\n\n"; error_msg += tr("You can only run multiple profiles for different users.").toStdString(); log_file->write_report(error_msg, "MphoneForm::newUsers", LOG_NORMAL, LOG_WARNING); ui->cb_display_msg(error_msg, MSG_WARNING); } } else { log_file->write_report(error_msg, "MphoneForm::newUsers", LOG_NORMAL, LOG_CRITICAL); ui->cb_display_msg(error_msg, MSG_CRITICAL); } progressStep++; } progress.setValue(add_profile_list.size()); populateBuddyList(); updateUserComboBox(); updateRegStatus(); updateMwi(); updateServicesStatus(); updateSysTrayStatus(); updateMenuStatus(); updateState(); call_history->clear_num_missed_calls(); } void MphoneForm::updateUserComboBox() { QString current_user; if (userComboBox->count() == 0) { // The last used profile current_user = sys_config->get_last_used_profile().c_str(); } else { // Keep the current active profile current_user = userComboBox->currentText(); } ((t_gui *)ui)->fill_user_combo(userComboBox); // If previous selected user is still active, make it the current user for (int i = 0; i < userComboBox->count(); i++) { if (userComboBox->itemText(i) == current_user) { userComboBox->setCurrentIndex(i); } } } void MphoneForm::updateSipUdpPort() { ((t_gui *)ui)->cb_show_msg(sysSettingsForm, tr("You have changed the SIP UDP port. This setting will only become "\ "active when you restart Twinkle.").toStdString(), MSG_INFO); } void MphoneForm::updateRtpPorts() { phone->init_rtp_ports(); } void MphoneForm::updateStunSettings(t_user *user_config) { string s; if (!phone->stun_discover_nat(user_config, s)) { // Warn user that the STUN settings will not work. ((t_gui *)ui)->cb_show_msg(this, s, MSG_WARNING); } if (!user_config->get_use_stun()) { // Disable STUN phone->disable_stun(user_config); } // Synchronize the sending of NAT keep alives with the user profile settings. phone->sync_nat_keepalive(user_config); } void MphoneForm::updateAuthCache(t_user *user_config, const string &realm) { phone->remove_cached_credentials(user_config, realm); } void MphoneForm::unsubscribeMWI(t_user *user_config) { phone->pub_unsubscribe_mwi(user_config); } void MphoneForm::subscribeMWI(t_user *user_config) { phone->pub_subscribe_mwi(user_config); } void MphoneForm::viewLog() { if (!logViewForm) { logViewForm = new LogViewForm(NULL); MEMMAN_NEW(logViewForm); } logViewForm->show(); } void MphoneForm::updateLog(bool log_zapped) { if (logViewForm) logViewForm->update(log_zapped); } void MphoneForm::viewHistory() { if (!historyForm) { historyForm = new HistoryForm(NULL); MEMMAN_NEW(historyForm); } connect(historyForm, SIGNAL(call(t_user *, const QString &, const QString &, bool)), this, SLOT(phoneInvite(t_user *, const QString &, const QString &, bool))); historyForm->show(); } void MphoneForm::updateCallHistory() { if (historyForm) historyForm->update(); } QSystemTrayIcon *MphoneForm::getSysTray() { return sysTray; } // Execute call directly from the main window (press call button) void MphoneForm::quickCall() { string display, dest_str; t_user *from_user = phone->ref_user_profile( userComboBox->currentText().toStdString()); if (!from_user) { log_file->write_report("Cannot find user profile.", "MphoneForm::quickCall", LOG_NORMAL, LOG_CRITICAL); return; } ui->expand_destination(from_user, callComboBox->currentText().trimmed().toStdString(), display, dest_str); t_url dest(dest_str); if (dest.is_valid()) { QString destination = callComboBox->currentText(); addToCallComboBox(destination); if (inviteForm) inviteForm->addToInviteComboBox(destination); callComboBox->setFocus(); do_phoneInvite(from_user, display.c_str(), dest, "", false); } } // Add a destination to the list of callComboBox void MphoneForm::addToCallComboBox(const QString &destination) { // Remove duplicate entries for (int i = callComboBox->count() - 1; i >= 0; i--) { if (callComboBox->itemText(i) == destination) { callComboBox->removeItem(i); } } // Add entry callComboBox->insertItem(0, destination); callComboBox->setCurrentIndex(0); // Remove last entry is list exceeds maximum size if (callComboBox->count() > SIZE_REDIAL_LIST) { callComboBox->removeItem(callComboBox->count() - 1); } // Clearing the edit line must be done here as this function is // also called when a call is made through the inviteForm. // The insertItem puts the text also in the edit field. So it must // be cleared here. callComboBox->clearEditText(); } void MphoneForm::showAddressBook() { if (!getAddressForm) { getAddressForm = new GetAddressForm(this); getAddressForm->setModal(true); MEMMAN_NEW(getAddressForm); } connect(getAddressForm, SIGNAL(address(const QString &)), this, SLOT(selectedAddress(const QString &))); getAddressForm->show(); } void MphoneForm::selectedAddress(const QString &address) { callComboBox->setEditText(address); } // Enable/disable the various call widgets void MphoneForm::enableCallOptions(bool enable) { // Enable/disable widgets callInvite->setEnabled(enable); callPushButton->setEnabled(enable); callComboBox->setEnabled(enable); addressToolButton->setEnabled(enable); // Set focus on callComboBox if (enable) { callComboBox->setFocus(); } } void MphoneForm::keyPressEvent(QKeyEvent *e) { if (callPushButton->isEnabled()) { // Quick dial switch (e->key()) { case Qt::Key_Return: case Qt::Key_Enter: quickCall(); break; default: e->ignore(); } } else if (callDTMF->isEnabled()) { // DTMF keys switch (e->key()) { case Qt::Key_1: sendDTMF("1"); break; case Qt::Key_2: case Qt::Key_A: case Qt::Key_B: case Qt::Key_C: sendDTMF("2"); break; case Qt::Key_3: case Qt::Key_D: case Qt::Key_E: case Qt::Key_F: sendDTMF("3"); break; case Qt::Key_4: case Qt::Key_G: case Qt::Key_H: case Qt::Key_I: sendDTMF("4"); break; case Qt::Key_5: case Qt::Key_J: case Qt::Key_K: case Qt::Key_L: sendDTMF("5"); break; case Qt::Key_6: case Qt::Key_M: case Qt::Key_N: case Qt::Key_O: sendDTMF("6"); break; case Qt::Key_7: case Qt::Key_P: case Qt::Key_Q: case Qt::Key_R: case Qt::Key_S: sendDTMF("7"); break; case Qt::Key_8: case Qt::Key_T: case Qt::Key_U: case Qt::Key_V: sendDTMF("8"); break; case Qt::Key_9: case Qt::Key_W: case Qt::Key_X: case Qt::Key_Y: case Qt::Key_Z: sendDTMF("9"); break; case Qt::Key_0: case Qt::Key_Space: sendDTMF("0"); break; case Qt::Key_Asterisk: sendDTMF("*"); break; case Qt::Key_NumberSign: sendDTMF("#"); break; default: e->ignore(); } } else { e->ignore(); } } // QLabels do not have mouse click events. I want the status labels // to be clickable however. Explicitly check here if a status label has // been clicked. void MphoneForm::mouseReleaseEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton && e->type() == QEvent::MouseButtonRelease) { processLeftMouseButtonRelease(e); } else if (e->button() == Qt::RightButton && e->type() == QEvent::MouseButtonRelease) { processRightMouseButtonRelease(e); } else { e->ignore(); } } void MphoneForm::processLeftMouseButtonRelease(QMouseEvent *e) { if (statAaLabel->testAttribute(Qt::WA_UnderMouse)) { if (phone->ref_users().size() == 1) { bool enable = !serviceAutoAnswer->isChecked(); srvAutoAnswer(enable); serviceAutoAnswer->setChecked(enable); } else { srvAutoAnswer(); } } else if (statDndLabel->testAttribute(Qt::WA_UnderMouse)) { if (phone->ref_users().size() == 1) { bool enable = !serviceDnd->isChecked(); srvDnd(enable); serviceDnd->setChecked(enable); } else { srvDnd(); } } else if (statCfLabel->testAttribute(Qt::WA_UnderMouse)) { srvRedirect(); } else if (statMWILabel->testAttribute(Qt::WA_UnderMouse)) { popupMenuVoiceMail(e->globalPos()); } else if (statMissedLabel->testAttribute(Qt::WA_UnderMouse)) { // Open the history form, when the user clicks on the // missed calls indication. viewHistory(); } else if (statRegLabel->testAttribute(Qt::WA_UnderMouse)) { // Fetch registration status phoneShowRegistrations(); } else if (crypt1Label->testAttribute(Qt::WA_UnderMouse)) { processCryptLabelClick(0); } else if (crypt2Label->testAttribute(Qt::WA_UnderMouse)) { processCryptLabelClick(1); } else { e->ignore(); } } void MphoneForm::processRightMouseButtonRelease(QMouseEvent *e) { e->ignore(); } void MphoneForm::processCryptLabelClick(int line) { t_audio_session *as = phone->get_line(line)->get_audio_session(); if (!as) return; if (as->get_zrtp_sas_confirmed()) { phoneResetZrtpSasConfirmation(line); } else { phoneConfirmZrtpSas(line); } } // Show popup menu to access voice mail void MphoneForm::popupMenuVoiceMail(const QPoint &pos) { QMenu menu(this); QIcon vmIcon(QPixmap(":/icons/images/mwi_none16.png")); vmIcon.addPixmap(QPixmap(":/icons/images/mwi_none16_dis.png"), QIcon::Disabled); listuser_list = phone->ref_users(); map vm; for (list::iterator i = user_list.begin(); i != user_list.end(); ++i) { QString address = (*i)->get_mwi_vm_address().c_str(); QString entry = (*i)->get_profile_name().c_str(); entry += " - "; if (address.isEmpty()) { entry += tr("not provisioned"); } else { entry += address; } QAction* act = menu.addAction(vmIcon, entry); if (address.isEmpty()) { act->setEnabled(false); } vm.insert(make_pair(act, *i)); } QAction* selected; // If multiple profiles are active, then show the popup menu. // If one profile is active, then call voice mail immediately. if (user_list.size() > 1) { selected = menu.exec(pos); if (selected == nullptr) return; } else { if (vm.begin()->second->get_mwi_vm_address().empty()) { ui->cb_show_msg( tr("You must provision your voice mail address in your " "user profile, before you can access it.").toStdString(), MSG_INFO); return; } selected = vm.begin()->first; } // Call can only be made if line is idle int line = phone->get_active_line(); if (phone->get_line_state(line) == LS_BUSY) { ui->cb_show_msg(tr("The line is busy. Cannot access voice mail.").toStdString(), MSG_WARNING); return; } t_user *selectedUser = vm[selected]; string display, dest_str; ui->expand_destination(selectedUser, selectedUser->get_mwi_vm_address(), display, dest_str); t_url dest(dest_str); if (dest.is_valid()) { QString destination = selectedUser->get_mwi_vm_address().c_str(); addToCallComboBox(destination); if (inviteForm) inviteForm->addToInviteComboBox(destination); callComboBox->setFocus(); do_phoneInvite(selectedUser, display.c_str(), dest, "", false); } else { QString msg(tr("The voice mail address %1 is an invalid address. " "Please provision a valid address in your user profile.")); ui->cb_show_msg(msg.arg(selectedUser->get_mwi_vm_address().c_str()).toStdString(), MSG_CRITICAL); } } void MphoneForm::popupMenuVoiceMail(void) { popupMenuVoiceMail(QCursor::pos()); } void MphoneForm::showDisplay(bool on) { if (on) { displayGroupBox->show(); } else { int hDisplay = displayGroupBox->height(); displayGroupBox->hide(); if (hDisplay < minimumHeight()) { setMinimumHeight(minimumHeight() - hDisplay); } resize(width(), minimumHeight()); } viewDisplay = on; viewDisplayAction->setChecked(on); } void MphoneForm::showBuddyList(bool on) { if (on) { buddyListView->show(); } else { buddyListView->hide(); } viewBuddyList = on; viewBuddyListAction->setChecked(on); } void MphoneForm::showCompactLineStatus(bool on) { if (on) { int hLabels = fromhead1Label->height() + tohead1Label->height() + subjecthead1Label->height() + fromhead2Label->height() + tohead2Label->height() + subjecthead2Label->height(); fromhead1Label->hide(); tohead1Label->hide(); subjecthead1Label->hide(); from1Label->hide(); to1Label->hide(); subject1Label->hide(); photo1Label->hide(); fromhead2Label->hide(); tohead2Label->hide(); subjecthead2Label->hide(); from2Label->hide(); to2Label->hide(); subject2Label->hide(); photo2Label->hide(); if (hLabels < minimumHeight()) { setMinimumHeight(minimumHeight() - hLabels); } resize(width(), minimumHeight()); } else { fromhead1Label->show(); tohead1Label->show(); subjecthead1Label->show(); from1Label->show(); to1Label->show(); subject1Label->show(); fromhead2Label->show(); tohead2Label->show(); subjecthead2Label->show(); from2Label->show(); to2Label->show(); subject2Label->show(); } viewCompactLineStatus = on; //viewCompactLineStatusAction->setOn(on); } bool MphoneForm::getViewDisplay() { return viewDisplay; } bool MphoneForm::getViewBuddyList() { return viewBuddyList; } bool MphoneForm::getViewCompactLineStatus() { return viewCompactLineStatus; } void MphoneForm::populateBuddyList() { buddyListView->clear(); list user_list = phone->ref_users(); for (list::iterator i = user_list.begin(); i != user_list.end(); ++i) { t_presence_epa *epa = phone->ref_presence_epa(*i); if (!epa) continue; BLViewUserItem *profileItem = new BLViewUserItem(buddyListView, epa); t_buddy_list *buddy_list = phone->ref_buddy_list(*i); list *buddies = buddy_list->get_records(); for (list::iterator bit = buddies->begin(); bit != buddies->end(); ++bit) { QString name = bit->get_name().c_str(); new BuddyListViewItem(profileItem, &(*bit)); } // profileItem->setOpen(true); } buddyListView->expandAll(); } void MphoneForm::showBuddyListPopupMenu(const QPoint &pos) { QTreeWidgetItem* item = buddyListView->currentItem(); if (!item) return; BuddyListViewItem *buddyItem = dynamic_cast(item); if (buddyItem) { buddyPopupMenu->popup(buddyListView->mapToGlobal(pos)); } else { buddyListPopupMenu->popup(buddyListView->mapToGlobal(pos)); } } void MphoneForm::doCallBuddy() { QTreeWidgetItem *qitem = buddyListView->currentItem(); BuddyListViewItem *item = dynamic_cast(qitem); if (!item) return; t_buddy *buddy = item->get_buddy(); t_user *user_config = buddy->get_user_profile(); phoneInvite(user_config, buddy->get_sip_address().c_str(), "", false); } void MphoneForm::doMessageBuddy(QTreeWidgetItem *qitem) { BuddyListViewItem *item = dynamic_cast(qitem); if (!item) return; t_buddy *buddy = item->get_buddy(); startMessageSession(buddy); } void MphoneForm::doMessageBuddy() { QTreeWidgetItem *item = buddyListView->currentItem(); doMessageBuddy(item); } void MphoneForm::doEditBuddy() { QTreeWidgetItem *qitem = buddyListView->currentItem(); BuddyListViewItem *item = dynamic_cast(qitem); if (!item) return; t_buddy *buddy = item->get_buddy(); BuddyForm *form = new BuddyForm(this); form->setModal(true); form->setAttribute(Qt::WA_DeleteOnClose, true); // Do not call MEMMAN as this form will be deleted automatically. form->showEdit(*buddy); } void MphoneForm::doDeleteBuddy() { QTreeWidgetItem *qitem = buddyListView->currentItem(); BuddyListViewItem *item = dynamic_cast(qitem); if (!item) return; t_buddy *buddy = item->get_buddy(); t_buddy_list *buddy_list = buddy->get_buddy_list(); // Delete the list item before deleting the buddy as // deleting the item will detach the item from the buddy. delete item; if (buddy->is_presence_terminated()) { buddy_list->del_buddy(*buddy); } else { buddy->unsubscribe_presence(true); } string err_msg; if (!buddy_list->save(err_msg)) { QString msg = tr("Failed to save buddy list: %1").arg(err_msg.c_str()); ((t_gui *)ui)->cb_show_msg(this, msg.toStdString(), MSG_CRITICAL); } } void MphoneForm::doAddBuddy() { QTreeWidgetItem *qitem = buddyListView->currentItem(); BLViewUserItem *item = dynamic_cast(qitem); if (!item) return; t_phone_user *pu = item->get_presence_epa()->get_phone_user(); if (!pu) return; t_buddy_list *buddy_list = pu->get_buddy_list(); if (!buddy_list) return; BuddyForm *form = new BuddyForm(this); form->setModal(true); form->setAttribute( Qt::WA_DeleteOnClose, true ); // Do not call MEMMAN as this form will be deleted automatically. form->showNew(*buddy_list, item); } void MphoneForm::doAvailabilityOffline() { QTreeWidgetItem *qitem = buddyListView->currentItem(); BLViewUserItem *item = dynamic_cast(qitem); if (!item) return; t_phone_user *pu = item->get_presence_epa()->get_phone_user(); if (!pu) return; pu->publish_presence(t_presence_state::ST_BASIC_CLOSED); } void MphoneForm::doAvailabilityOnline() { QTreeWidgetItem *qitem = buddyListView->currentItem(); BLViewUserItem *item = dynamic_cast(qitem); if (!item) return; t_phone_user *pu = item->get_presence_epa()->get_phone_user(); if (!pu) return; pu->publish_presence(t_presence_state::ST_BASIC_OPEN); } void MphoneForm::DiamondcardSignUp() { DiamondcardProfileForm *f = new DiamondcardProfileForm(this); f->setModal(true); f->setAttribute( Qt::WA_DeleteOnClose, true ); connect(f, SIGNAL(newDiamondcardProfile(const QString&)), this, SLOT(newDiamondcardUser(const QString &))); f->show(NULL); } void MphoneForm::newDiamondcardUser(const QString &filename) { list profileFilenames; list users = phone->ref_users(); for (list::const_iterator it = users.begin(); it != users.end(); ++it) { t_user *user = *it; profileFilenames.push_back(user->get_filename()); } profileFilenames.push_back(filename.toStdString()); newUsers(profileFilenames); } void MphoneForm::DiamondcardAction(t_dc_action action, int userIdx) { list diamondcard_users = diamondcard_get_users(phone); vector v(diamondcard_users.begin(), diamondcard_users.end()); if (userIdx < 0 || (unsigned int)userIdx >= v.size()) return; t_user *user = v[userIdx]; QString url(diamondcard_url(action, user->get_name(), user->get_auth_pass()).c_str()); ((t_gui *)ui)->open_url_in_browser(url); } void MphoneForm::DiamondcardRecharge() { DiamondcardAction(DC_ACT_RECHARGE, static_cast(sender())->data().toInt()); } void MphoneForm::DiamondcardBalanceHistory() { DiamondcardAction(DC_ACT_BALANCE_HISTORY, static_cast(sender())->data().toInt()); } void MphoneForm::DiamondcardCallHistory() { DiamondcardAction(DC_ACT_CALL_HISTORY, static_cast(sender())->data().toInt()); } void MphoneForm::DiamondcardAdminCenter() { DiamondcardAction(DC_ACT_ADMIN_CENTER, static_cast(sender())->data().toInt()); } void MphoneForm::whatsThis() { QWhatsThis::enterWhatsThisMode(); } void MphoneForm::sysTrayIconClicked(QSystemTrayIcon::ActivationReason reason) { if (reason == QSystemTrayIcon::Trigger || reason == QSystemTrayIcon::DoubleClick) { if (sys_config->get_gui_hide_on_close()) setVisible(!isVisible()); else activateWindow(); } } bool MphoneForm::event(QEvent * event) { if (event->type() == QEvent::WindowActivate || event->type() == QEvent::WindowDeactivate) osdWindow->setVisible(shouldDisplayOSD()); return QMainWindow::event(event); } bool MphoneForm::shouldDisplayOSD() { if (QApplication::activeWindow() == this) return false; t_line_substate ss; ss = phone->get_line_substate(phone->get_active_line()); switch (ss) { case LSSUB_ANSWERING: case LSSUB_ESTABLISHED: case LSSUB_OUTGOING_PROGRESS: break; default: return false; } if (!sys_config->get_gui_show_call_osd()) return false; return true; } void MphoneForm::updateOSD() { t_line_substate ss; int line; bool osdDisplayed; struct timeval t; unsigned long duration; t_user *user_config; t_call_record cr; osdDisplayed = shouldDisplayOSD(); osdWindow->setVisible(osdDisplayed); line = phone->get_active_line(); ss = phone->get_line_substate(line); user_config = phone->get_line_user(line); cr = phone->get_call_hist(line); if (ss == LSSUB_ESTABLISHED) { // Calculate duration of call gettimeofday(&t, nullptr); duration = t.tv_sec - cr.time_answer; osdWindow->setTime(QString::fromStdString(timer2str(duration))); osdWindow->setMuted(phone->is_line_muted(line)); } else { osdWindow->setTime(lineSubstate2str(line)); } if (ss != LSSUB_IDLE && user_config != nullptr) { std::string address; address = (cr.direction == t_call_record::DIR_IN ? ui->format_sip_address(user_config, cr.from_display, cr.from_uri) : ui->format_sip_address(user_config, cr.to_display, cr.to_uri)); osdWindow->setCaller(QString::fromStdString(address)); } } void MphoneForm::osdMuteClicked() { ((t_gui *)ui)->action_mute(!phone->is_line_muted(phone->get_active_line())); updateState(); } twinkle-1.10.1/src/gui/mphoneform.h000066400000000000000000000155441277565361200171740ustar00rootroot00000000000000#ifndef MPHONEFORM_UI_H #define MPHONEFORM_UI_H #include #include "ui_mphoneform.h" #include "phone.h" #include "dtmfform.h" #include "inviteform.h" #include "redirectform.h" #include "termcapform.h" #include "srvredirectform.h" #include "userprofileform.h" #include "transferform.h" #include "syssettingsform.h" #include "logviewform.h" #include "historyform.h" #include "selectuserform.h" #include "selectprofileform.h" #include #include #include #include "im/msg_session.h" #include "messageformview.h" #include "buddylistview.h" #include "diamondcard.h" class t_phone; extern t_phone *phone; class OSD; class IncomingCallPopup; class MphoneForm : public QMainWindow, public Ui::MphoneForm { Q_OBJECT public: MphoneForm(QWidget* parent = 0); ~MphoneForm(); public: QString getMWIStatus( const t_mwi & mwi, bool & msg_waiting ) const; QSystemTrayIcon * getSysTray(); bool getViewDisplay(); bool getViewBuddyList(); bool getViewCompactLineStatus(); protected: virtual void closeEvent( QCloseEvent * e ) override; virtual bool event(QEvent * event) override; public slots: void fileExit(); void display( const QString & s ); void displayHeader(); void showLineTimer( int line ); void showLineTimer1(); void showLineTimer2(); void updateLineTimer( int line ); void updateLineEncryptionState( int line ); void updateLineStatus( int line ); void updateState(); void updateRegStatus(); void flashMWI(); void updateMwi(); void updateServicesStatus(); void updateMissedCallStatus( int num_missed_calls ); void updateSysTrayStatus(); void updateMenuStatus(); void updateDiamondcardMenu(); void removeDiamondcardAction( QAction* & id ); void removeDiamondcardMenu( QMenu * & menu ); void phoneRegister(); void do_phoneRegister( list user_list ); void phoneDeregister(); void do_phoneDeregister( list user_list ); void phoneDeregisterAll(); void do_phoneDeregisterAll( list user_list ); void phoneShowRegistrations(); void phoneInvite( t_user * user_config, const QString & dest, const QString & subject, bool anonymous ); void phoneInvite( const QString & dest, const QString & subject, bool anonymous ); void phoneInvite(); void do_phoneInvite( t_user * user_config, const QString & display, const t_url & destination, const QString & subject, bool anonymous ); void phoneRedial( void ); void phoneAnswer(); void phoneAnswerFromSystrayPopup(); void phoneBye(); void phoneReject(); void phoneRejectFromSystrayPopup(); void phoneRedirect( const list & contacts ); void phoneRedirect(); void do_phoneRedirect( const list & destinations ); void phoneTransfer( const string & dest, t_transfer_type transfer_type ); void phoneTransfer(); void do_phoneTransfer( const t_display_url & destination, t_transfer_type transfer_type ); void do_phoneTransferLine(); void phoneHold( bool on ); void phoneConference(); void phoneMute( bool on ); void phoneTermCap( const QString & dest ); void phoneTermCap(); void do_phoneTermCap( t_user * user_config, const t_url & destination ); void phoneDTMF(); void sendDTMF( const QString & digits ); void startMessageSession( void ); void startMessageSession( t_buddy * buddy ); void phoneConfirmZrtpSas( int line ); void phoneConfirmZrtpSas(); void phoneResetZrtpSasConfirmation( int line ); void phoneResetZrtpSasConfirmation(); void phoneEnableZrtp( bool on ); void phoneZrtpGoClearOk( unsigned short line ); void line1rbChangedState( bool on ); void line2rbChangedState( bool on ); void actionLine1Toggled( bool on ); void actionLine2Toggled( bool on ); void srvDnd( bool on ); void srvDnd(); void do_srvDnd_enable( list user_list ); void do_srvDnd_disable( list user_list ); void srvAutoAnswer( bool on ); void srvAutoAnswer(); void do_srvAutoAnswer_enable( list user_list ); void do_srvAutoAnswer_disable( list user_list ); void srvRedirect(); void do_srvRedirect( t_user * user_config, const list & always, const list & busy, const list & noanswer ); void about(); void aboutQt(); void manual(); void editUserProfile(); void editSysSettings(); void selectProfile(); void newUsers( const list & profiles ); void updateUserComboBox(); void updateSipUdpPort(); void updateRtpPorts(); void updateStunSettings( t_user * user_config ); void updateAuthCache( t_user * user_config, const string & realm ); void unsubscribeMWI( t_user * user_config ); void subscribeMWI( t_user * user_config ); void viewLog(); void updateLog( bool log_zapped ); void viewHistory(); void updateCallHistory(); void quickCall(); void addToCallComboBox( const QString & destination ); void showAddressBook(); void selectedAddress( const QString & address ); void enableCallOptions( bool enable ); virtual void keyPressEvent( QKeyEvent * e ) override; virtual void mouseReleaseEvent( QMouseEvent * e ) override; void processLeftMouseButtonRelease( QMouseEvent * e ); void processRightMouseButtonRelease( QMouseEvent * e ); void processCryptLabelClick( int line ); void popupMenuVoiceMail( const QPoint & pos ); void popupMenuVoiceMail( void ); void showDisplay( bool on ); void showBuddyList( bool on ); void showCompactLineStatus( bool on ); void populateBuddyList(); void showBuddyListPopupMenu( const QPoint & pos ); void doCallBuddy(); void doMessageBuddy( QTreeWidgetItem * qitem ); void doMessageBuddy(); void doEditBuddy(); void doDeleteBuddy(); void doAddBuddy(); void doAvailabilityOffline(); void doAvailabilityOnline(); void DiamondcardSignUp(); void newDiamondcardUser( const QString & filename ); void DiamondcardAction( t_dc_action action, int userIdx ); void DiamondcardRecharge(); void DiamondcardBalanceHistory(); void DiamondcardCallHistory(); void DiamondcardAdminCenter(); void whatsThis(); void sysTrayIconClicked(QSystemTrayIcon::ActivationReason); void osdMuteClicked(); private: void init(); void destroy(); bool shouldDisplayOSD(); void updateOSD(); QString lineSubstate2str( int line ); private: QTimer tmrFlashMWI; GetAddressForm *getAddressForm; SelectProfileForm *selectProfileForm; SelectUserForm *selectUserForm; HistoryForm *historyForm; TransferForm *transferForm; UserProfileForm *userProfileForm; SrvRedirectForm *srvRedirectForm; TermCapForm *termCapForm; RedirectForm *redirectForm; InviteForm *inviteForm; DtmfForm *dtmfForm; SysSettingsForm *sysSettingsForm; QStringList displayContents; LogViewForm *logViewForm; QSystemTrayIcon *sysTray; QTimer *lineTimer1; QTimer *lineTimer2; QTimer *hideLineTimer1; QTimer *hideLineTimer2; bool viewDisplay; bool viewCompactLineStatus; bool mwiFlashStatus; QMenu *buddyPopupMenu; QMenu *buddyListPopupMenu; QMenu *changeAvailabilityPopupMenu; bool viewBuddyList; OSD *osdWindow; IncomingCallPopup *incomingCallPopup; }; #endif twinkle-1.10.1/src/gui/mphoneform.ui000066400000000000000000002306431277565361200173610ustar00rootroot00000000000000 MphoneForm true 0 0 733 693 0 0 500 693 Twinkle :/icons/images/twinkle48.png:/icons/images/twinkle48.png false false true Qt::Horizontal 150 0 0 0 Qt::CustomContextMenu You can create a separate buddy list for each user profile. You can only see availability of your buddies and publish your own availability if your provider offers a presence server. true Buddy list &Call: false callComboBox 0 0 The address that you want to call. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. true 10 QComboBox::NoInsert true Qt::TabFocus Address book Select an address from the address book. :/icons/images/kontact_contacts.png:/icons/images/kontact_contacts.png F10 Dial the address. Dial true &User: false userComboBox 0 0 The user that will make the call. 0 0 Auto answer indication. :/icons/images/auto_answer.png false 0 0 Call redirect indication. :/icons/images/cf.png false 0 0 Do not disturb indication. :/icons/images/cancel.png false 0 0 Message waiting indication. :/icons/images/mwi_none16.png false 0 0 Missed call indication. :/icons/images/missed.png false 0 0 Registration status. :/icons/images/twinkle16.png false 0 0 Display 0 0 true 0 0 Line status true 0 0 Click to switch to line 1. Line &1: Alt+1 true 0 0 From: false 21 0 0 To: false 21 0 0 Subject: false 21 0 0 false 0 0 idle Qt::RichText false 0 0 false 0 0 false 0 0 false 0 0 false 0 0 false 0 0 Short authentication string sas false 0 0 Audio codec g711a/g711a Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false 0 0 Courier New Call duration 0:00:00 false Qt::NoFocus sip:from true Qt::NoFocus sip:to true Qt::NoFocus subject true 0 0 70 98 70 98 QFrame::StyledPanel photo false 0 0 Click to switch to line 2. Line &2: Alt+2 0 0 From: false 21 0 0 To: false 21 0 0 Subject: false 21 0 0 false 0 0 idle Qt::RichText false 0 0 false 0 0 false 0 0 false 0 0 false 0 0 false 0 0 Short authentication string sas false 0 0 Audio codec g711a/g711a Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false 0 0 Courier New Call duration 0:00:00 false Qt::NoFocus sip:from true Qt::NoFocus sip:to true Qt::NoFocus subject true 0 0 70 98 70 98 QFrame::StyledPanel photo false Qt::ToolButtonTextUnderIcon false false false false TopToolBarArea false 0 0 733 17 &File &Edit C&all Activate line &Message &Registration &Services &View Diamondcard &Help :/icons/images/exit.png:/icons/images/exit.png &Quit Quit Ctrl+Q fileExitAction :/icons/images/twinkle16.png:/icons/images/twinkle16.png &About Twinkle About Twinkle helpAboutAction :/icons/images/invite.png:/icons/images/invite.png &Call... Call Call someone F5 callInvite :/icons/images/answer.png:/icons/images/answer.png &Answer Answer Answer incoming call F6 callAnswer :/icons/images/bye.png:/icons/images/bye.png &Bye Bye Release call Esc callBye :/icons/images/reject.png:/icons/images/reject.png &Reject Reject Reject incoming call F8 callReject true :/icons/images/hold.png:/icons/images/hold.png &Hold Hold Put a call on hold, or retrieve a held call callHold :/icons/images/redirect.png:/icons/images/redirect.png R&edirect... Redirect Redirect incoming call without answering callRedirect :/icons/images/dtmf.png:/icons/images/dtmf.png &Dtmf... Dtmf Open keypad to enter digits for voice menu's callDTMF :/icons/images/twinkle16.png:/icons/images/twinkle16.png &Register Register regRegister :/icons/images/twinkle16-disabled.png:/icons/images/twinkle16-disabled.png &Deregister Deregister Deregister this device regDeregister :/icons/images/reg-query.png:/icons/images/reg-query.png &Show registrations Show registrations regShow &Terminal capabilities... Terminal capabilities Request terminal capabilities from someone callTermCap true :/icons/images/cancel.png:/icons/images/cancel.png &Do not disturb Do not disturb serviceDnd :/icons/images/cf.png:/icons/images/cf.png Call &redirection... Call redirection serviceRedirection :/icons/images/redial.png:/icons/images/redial.png &Redial Redial Repeat last call F12 callRedial :/icons/images/qt-logo.png:/icons/images/qt-logo.png About &Qt About Qt helpAboutQtAction :/icons/images/penguin-small.png:/icons/images/penguin-small.png &User profile... User profile editUserProfileAction :/icons/images/conference.png:/icons/images/conference.png &Conference Conf Join two calls in a 3-way conference callConference true :/icons/images/mute.png:/icons/images/mute.png &Mute Mute Mute a call callMute true :/icons/images/transfer.png:/icons/images/transfer.png Trans&fer... Xfer Transfer call callTransfer :/icons/images/settings.png:/icons/images/settings.png &System settings... System settings editSysSettingsAction registrationAction :/icons/images/twinkle16-disabled.png:/icons/images/twinkle16-disabled.png Deregister &all Deregister all Deregister all your registered devices regDeregisterAll true :/icons/images/auto_answer.png:/icons/images/auto_answer.png &Auto answer Auto answer serviceAutoAnswer :/icons/images/log_small.png:/icons/images/log_small.png &Log... Log viewLogAction :/icons/images/missed.png:/icons/images/missed.png Call &history... Call history F9 viewCall_HistoryAction :/icons/images/penguin-small.png:/icons/images/penguin-small.png &Change user ... Change user ... Activate or de-activate users fileChangeUserAction :/icons/images/contexthelp.png:/icons/images/contexthelp.png What's &This? What's This? Shift+F1 helpWhats_ThisAction false Activate line Activate line callActivate_LineAction true true &Display Display viewDisplayAction :/icons/images/mwi_none16.png:/icons/images/mwi_none16.png &Voice mail Voice mail Access voice mail F11 servicesVoice_mailAction :/icons/images/message.png:/icons/images/message.png Instant &message... Msg Instant message actionSendMsg true true &Buddy list Buddy list Buddy list viewBuddyListAction &Manual Manual helpManualAction &Sign up... Sign up Sign up diamondcardSign_upAction true true Line 1 actionLine1 true Line 2 actionLine2 actgrActivateLine callComboBox addressToolButton callPushButton displayTextEdit line1RadioButton line2RadioButton userComboBox from1Label subject1Label to1Label subject2Label from2Label to2Label phone.h ui_dtmfform.h ui_inviteform.h ui_redirectform.h ui_termcapform.h ui_srvredirectform.h ui_userprofileform.h ui_transferform.h ui_syssettingsform.h ui_logviewform.h ui_historyform.h ui_selectuserform.h ui_selectprofileform.h qevent.h im/msg_session.h messageformview.h buddylistview.h diamondcard.h helpWhats_ThisAction triggered() MphoneForm whatsThis() -1 -1 20 20 addressToolButton clicked() MphoneForm showAddressBook() 634 127 20 20 callPushButton clicked() MphoneForm quickCall() 664 127 20 20 fileChangeUserAction triggered() MphoneForm selectProfile() -1 -1 20 20 viewCall_HistoryAction triggered() MphoneForm viewHistory() -1 -1 20 20 viewLogAction triggered() MphoneForm viewLog() -1 -1 20 20 regDeregisterAll triggered() MphoneForm phoneDeregisterAll() -1 -1 20 20 editSysSettingsAction triggered() MphoneForm editSysSettings() -1 -1 20 20 callMute toggled(bool) MphoneForm phoneMute(bool) -1 -1 20 20 callConference triggered() MphoneForm phoneConference() -1 -1 20 20 editUserProfileAction triggered() MphoneForm editUserProfile() -1 -1 20 20 helpAboutQtAction triggered() MphoneForm aboutQt() -1 -1 20 20 helpAboutAction triggered() MphoneForm about() -1 -1 20 20 callRedial triggered() MphoneForm phoneRedial() -1 -1 20 20 serviceRedirection triggered() MphoneForm srvRedirect() -1 -1 20 20 regShow triggered() MphoneForm phoneShowRegistrations() -1 -1 20 20 regDeregister triggered() MphoneForm phoneDeregister() -1 -1 20 20 regRegister triggered() MphoneForm phoneRegister() -1 -1 20 20 callHold toggled(bool) MphoneForm phoneHold(bool) -1 -1 20 20 callReject triggered() MphoneForm phoneReject() -1 -1 20 20 callRedirect triggered() MphoneForm phoneRedirect() -1 -1 20 20 callInvite triggered() MphoneForm phoneInvite() -1 -1 20 20 callDTMF triggered() MphoneForm phoneDTMF() -1 -1 20 20 callBye triggered() MphoneForm phoneBye() -1 -1 20 20 callAnswer triggered() MphoneForm phoneAnswer() -1 -1 20 20 callTermCap triggered() MphoneForm phoneTermCap() -1 -1 20 20 line2RadioButton toggled(bool) MphoneForm line2rbChangedState(bool) 309 597 20 20 line1RadioButton toggled(bool) MphoneForm line1rbChangedState(bool) 309 491 20 20 fileExitAction triggered() MphoneForm fileExit() -1 -1 20 20 actionLine1 toggled(bool) MphoneForm actionLine1Toggled(bool) -1 -1 20 20 actionLine2 toggled(bool) MphoneForm actionLine2Toggled(bool) -1 -1 20 20 viewDisplayAction toggled(bool) MphoneForm showDisplay(bool) -1 -1 20 20 callTransfer triggered() MphoneForm phoneTransfer() -1 -1 20 20 servicesVoice_mailAction triggered() MphoneForm popupMenuVoiceMail() -1 -1 20 20 actionSendMsg triggered() MphoneForm startMessageSession() -1 -1 20 20 viewBuddyListAction toggled(bool) MphoneForm showBuddyList(bool) -1 -1 20 20 helpManualAction triggered() MphoneForm manual() -1 -1 20 20 diamondcardSign_upAction triggered() MphoneForm DiamondcardSignUp() -1 -1 20 20 buddyListView itemDoubleClicked(QTreeWidgetItem*,int) MphoneForm doMessageBuddy(QTreeWidgetItem*) 130 206 3 195 buddyListView customContextMenuRequested(QPoint) MphoneForm showBuddyListPopupMenu(QPoint) 98 285 3 301 doMessageBuddy(QTreeWidgetItem*) showBuddyListPopupMenu(QPoint) twinkle-1.10.1/src/gui/numberconversionform.cpp000066400000000000000000000035251277565361200216330ustar00rootroot00000000000000#include "numberconversionform.h" #include "gui.h" /* * Constructs a NumberConversionForm which is a child of 'parent', with the * name 'name' and widget flags set to 'f' * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ NumberConversionForm::NumberConversionForm(QWidget* parent) : QDialog(parent) { setupUi(this); } /* * Destroys the object and frees any allocated resources */ NumberConversionForm::~NumberConversionForm() { // no need to delete child widgets, Qt does it all for us } void NumberConversionForm::init() { QRegExp rxNoAtSign("[^@]*"); exprLineEdit->setValidator(new QRegExpValidator(rxNoAtSign, this)); replaceLineEdit->setValidator(new QRegExpValidator(rxNoAtSign, this)); } int NumberConversionForm::exec(QString &expr, QString &replace) { exprLineEdit->setText(expr); replaceLineEdit->setText(replace); int retval = QDialog::exec(); if (retval == QDialog::Accepted) { expr = exprLineEdit->text(); replace = replaceLineEdit->text(); } return retval; } void NumberConversionForm::validate() { QString expr = exprLineEdit->text(); QString replace = replaceLineEdit->text(); if (expr.isEmpty()) { ((t_gui *)ui)->cb_show_msg(this, tr("Match expression may not be empty.").toStdString(), MSG_CRITICAL); exprLineEdit->setFocus(); exprLineEdit->selectAll(); return; } if (replace.isEmpty()) { ((t_gui *)ui)->cb_show_msg(this, tr("Replace value may not be empty.").toStdString(), MSG_CRITICAL); replaceLineEdit->setFocus(); replaceLineEdit->selectAll(); return; } try { std::regex re(expr.toStdString()); } catch (std::regex_error) { ((t_gui *)ui)->cb_show_msg(this, tr("Invalid regular expression.").toStdString(), MSG_CRITICAL); exprLineEdit->setFocus(); return; } accept(); } twinkle-1.10.1/src/gui/numberconversionform.h000066400000000000000000000006761277565361200213040ustar00rootroot00000000000000#ifndef NUMBERCONVERSIONFORM_H #define NUMBERCONVERSIONFORM_H #include #include "ui_numberconversionform.h" class NumberConversionForm : public QDialog, private Ui::NumberConversionForm { Q_OBJECT public: NumberConversionForm(QWidget* parent = 0); ~NumberConversionForm(); int exec(QString& expr, QString& replace); public slots: void validate(); private: void init(); }; #endif // NUMBERCONVERSIONFORM_H twinkle-1.10.1/src/gui/numberconversionform.ui000066400000000000000000000107151277565361200214650ustar00rootroot00000000000000 NumberConversionForm 0 0 436 122 Twinkle - Number conversion &Match expression: exprLineEdit false &Replace: replaceLineEdit false Perl style format string for the replacement number. Perl style regular expression matching the number format you want to modify. 20 20 QSizePolicy::Expanding Qt::Vertical 71 20 QSizePolicy::Expanding Qt::Horizontal &OK Alt+O &Cancel Alt+C exprLineEdit replaceLineEdit okPushButton cancelPushButton cancelPushButton clicked() NumberConversionForm reject() okPushButton clicked() NumberConversionForm validate() twinkle-1.10.1/src/gui/osd.cpp000066400000000000000000000045211277565361200161330ustar00rootroot00000000000000#include "osd.h" #include #include #include #include #include #include #include extern QSettings* g_gui_state; OSD::OSD(QObject* parent) : QObject(parent) { m_view = new QQuickView; m_view->setFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::ToolTip); m_view->rootContext()->setContextProperty("viewerWidget", m_view); m_view->setSource(QUrl("qrc:/qml/osd.qml")); positionWindow(); QObject* buttonHangup; buttonHangup = m_view->rootObject()->findChild("hangup"); connect(buttonHangup, SIGNAL(clicked()), this, SLOT(onHangupClicked())); m_caller = m_view->rootObject()->findChild("callerName"); m_time = m_view->rootObject()->findChild("callTime"); m_mute = m_view->rootObject()->findChild("mute"); connect(m_mute, SIGNAL(clicked()), this, SLOT(onMuteClicked())); connect(m_view->rootObject(), SIGNAL(moved()), this, SLOT(saveState())); } OSD::~OSD() { delete m_view; } void OSD::positionWindow() { QDesktopWidget* desktop = QApplication::desktop(); int x, y; int defaultX, defaultY; defaultX = desktop->width() - this->width() - 10; defaultY = 10; x = g_gui_state->value("osd/x", defaultX).toInt(); y = g_gui_state->value("osd/y", defaultY).toInt(); // Reset position if off screen if (x > desktop->width() || x < 0) x = defaultX; if (y > desktop->height() || y < 0) y = defaultY; m_view->setPosition(x, y); } void OSD::saveState() { QPoint pos = m_view->position(); g_gui_state->setValue("osd/x", pos.x()); g_gui_state->setValue("osd/y", pos.y()); } void OSD::onHangupClicked() { emit hangupClicked(); } void OSD::onMuteClicked() { emit muteClicked(); } void OSD::setMuted(bool muted) { QString path; if (muted) path = "qrc:/icons/images/osd_mic_off.png"; else path = "qrc:/icons/images/osd_mic_on.png"; m_mute->setProperty("image", path); } void OSD::setCaller(const QString& text) { m_caller->setProperty("text", text); } void OSD::setTime(const QString& timeText) { m_time->setProperty("text", timeText); } void OSD::move(int x, int y) { m_view->setPosition(x, y); } void OSD::show() { m_view->show(); } void OSD::hide() { m_view->hide(); } int OSD::width() const { return m_view->width(); } int OSD::height() const { return m_view->height(); } twinkle-1.10.1/src/gui/osd.h000066400000000000000000000016011277565361200155740ustar00rootroot00000000000000#ifndef OSD_H #define OSD_H #include #include // Must use forward declaration, otherwise build fails // due to double QMetaTypeID definition (wtf). // Hence I also cannot inherit from OSD_VIEWCLASS... class QQuickView; class QQuickItem; class OSD : public QObject { Q_OBJECT public: OSD(QObject* parent = 0); ~OSD(); void setCaller(const QString& text); void setTime(const QString& timeText); void setMuted(bool muted); void move(int x, int y); void show(); void hide(); int width() const; int height() const; void setVisible(bool v) { if (v) show(); else hide(); } private: void positionWindow(); public slots: void onHangupClicked(); void onMuteClicked(); void saveState(); signals: void hangupClicked(); void muteClicked(); private: QQuickView* m_view; QQuickItem* m_caller; QQuickItem* m_time; QQuickItem* m_mute; }; #endif // OSD_H twinkle-1.10.1/src/gui/qml/000077500000000000000000000000001277565361200154315ustar00rootroot00000000000000twinkle-1.10.1/src/gui/qml/ImageButton.qml000066400000000000000000000007461277565361200203710ustar00rootroot00000000000000import QtQuick 2.0 Rectangle { property alias image: img.source signal clicked color: "transparent" z: 2 MouseArea { id: mouseArea anchors.fill: parent onClicked: parent.clicked() } Image { id: img smooth: true anchors.fill: parent } states: State { name: "pressed"; when: mouseArea.pressed PropertyChanges { target: img; anchors.topMargin: 2; anchors.bottomMargin: -2 } } } twinkle-1.10.1/src/gui/qml/TextImageButton.qml000066400000000000000000000025131277565361200212300ustar00rootroot00000000000000import QtQuick 2.0 Rectangle { id: backgroundRect width: 150 height: 30 radius: 0 property alias image: img.source property alias text: text.text property alias color: backgroundRect.color signal clicked color: "red" z: 2 Image { id: img width: height anchors.top: parent.top anchors.topMargin: 2 anchors.bottom: parent.bottom anchors.bottomMargin: 2 anchors.left: parent.left anchors.leftMargin: 5 smooth: true source: "qrc:/qtquickplugin/images/template_image.png" } MouseArea { id: mouseArea anchors.fill: parent onClicked: parent.clicked() } Text { id: text text: "Button text" font.bold: true horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter anchors.right: parent.right anchors.rightMargin: 0 anchors.left: img.right anchors.leftMargin: 5 anchors.top: parent.top anchors.topMargin: 0 anchors.bottom: parent.bottom anchors.bottomMargin: 0 color: "white" font.pixelSize: 12 } states: State { name: "pressed"; when: mouseArea.pressed PropertyChanges { target: backgroundRect; color: Qt.darker(color) } } } twinkle-1.10.1/src/gui/qml/incoming_call.qml000066400000000000000000000033331277565361200207440ustar00rootroot00000000000000import QtQuick 2.0 Rectangle { id: rectanglePopup width: 400 height: 70 color: "black" signal moved Image { id: image1 anchors.bottom: parent.bottom anchors.bottomMargin: 5 anchors.top: parent.top anchors.topMargin: 5 anchors.left: parent.left anchors.leftMargin: 5 source: "qrc:/icons/images/twinkle48.png" width: height } Text { id: callerText objectName: "callerText" height: 22 color: "#ffffff" text: "... calling" anchors.top: parent.top anchors.topMargin: 8 anchors.left: image1.right anchors.leftMargin: 9 anchors.right: parent.right anchors.rightMargin: 10 font.pixelSize: 19 } TextImageButton { id: buttonAnswer objectName: "buttonAnswer" x: 74 y: 36 width: 120 height: 26 color: "#00aa00" radius: 7 text: qsTr("Answer") image: "qrc:/icons/images/popup_incoming_answer.png" } TextImageButton { id: buttonReject objectName: "buttonReject" y: 36 width: 120 height: 26 radius: 7 text: qsTr("Reject") anchors.left: buttonAnswer.right anchors.leftMargin: 15 image: "qrc:/icons/images/popup_incoming_reject.png" } MouseArea { anchors.fill: parent property real lastMouseX: 0 property real lastMouseY: 0 onPressed: { lastMouseX = mouseX lastMouseY = mouseY } onMouseXChanged: viewerWidget.x += (mouseX - lastMouseX) onMouseYChanged: viewerWidget.y += (mouseY - lastMouseY) } } twinkle-1.10.1/src/gui/qml/osd.qml000066400000000000000000000040261277565361200167330ustar00rootroot00000000000000import QtQuick 2.0 Rectangle { id: rectangleOsd width: 310 height: 55 color: "black" signal moved Image { id: image1 anchors.bottom: parent.bottom anchors.bottomMargin: 5 anchors.top: parent.top anchors.topMargin: 5 anchors.left: parent.left anchors.leftMargin: 5 source: "qrc:/icons/images/twinkle48.png" width: height } ImageButton { id: hangup objectName: "hangup" x: 262 anchors.bottom: parent.bottom anchors.bottomMargin: 15 anchors.top: parent.top anchors.topMargin: 15 anchors.right: parent.right anchors.rightMargin: 10 width: height image: "qrc:/icons/images/osd_hangup.png" } ImageButton { id: mute objectName: "mute" x: 222 width: height image: "qrc:/icons/images/osd_mic_on.png" anchors.bottomMargin: 15 anchors.topMargin: 15 anchors.top: parent.top anchors.bottom: parent.bottom anchors.rightMargin: 14 anchors.right: hangup.left } Text { id: callerName objectName: "callerName" x: 56 y: 5 width: 158 height: 21 text: "Caller name" clip: true verticalAlignment: Text.AlignVCenter font.bold: true font.pixelSize: 12 color: "white" } Text { id: callTime objectName: "callTime" x: 56 y: 27 width: 158 height: 20 text: "Time" clip: true verticalAlignment: Text.AlignVCenter font.pixelSize: 12 color: "white" } MouseArea { anchors.fill: parent property real lastMouseX: 0 property real lastMouseY: 0 onPressed: { lastMouseX = mouseX lastMouseY = mouseY } onMouseXChanged: viewerWidget.x += (mouseX - lastMouseX) onMouseYChanged: viewerWidget.y += (mouseY - lastMouseY) } } twinkle-1.10.1/src/gui/qml/qml.qrc000066400000000000000000000003161277565361200167310ustar00rootroot00000000000000 ImageButton.qml osd.qml TextImageButton.qml incoming_call.qml twinkle-1.10.1/src/gui/qt_translator.h000066400000000000000000000024611277565361200177110ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _QT_TRANSLATOR_H #define _QT_TRANSLATOR_H #include #include "translator.h" // This class provides the translation service from Qt to the // core of Twinkle. class t_qt_translator : public t_translator { public: t_qt_translator(QApplication *qa) : _qa(qa) {}; virtual string translate(const string &s) { return _qa->translate("TwinkleCore", s.c_str()).toStdString(); }; virtual string translate2(const string &context, const string &s) { return _qa->translate(context.c_str(), s.c_str()).toStdString(); }; private: QApplication *_qa; }; #endif twinkle-1.10.1/src/gui/redirectform.cpp000066400000000000000000000075661277565361200200470ustar00rootroot00000000000000#include "redirectform.h" //Added by qt3to4: #include /* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "audits/memman.h" #include "gui.h" RedirectForm::RedirectForm(QWidget* parent) : QDialog(parent) { setupUi(this); init(); } RedirectForm::~RedirectForm() { destroy(); } /* * Sets the strings of the subwidgets using the current * language. */ void RedirectForm::languageChange() { retranslateUi(this); } void RedirectForm::init() { // Keeps track of which address book tool button is clicked. nrAddressBook = 0; getAddressForm = 0; // Set toolbutton icons for disabled options. QIcon i; i = address1ToolButton->icon(); i.addPixmap(QPixmap(":/icons/images/kontact_contacts-disabled.png"), QIcon::Disabled); address1ToolButton->setIcon(i); address2ToolButton->setIcon(i); address3ToolButton->setIcon(i); } void RedirectForm::destroy() { if (getAddressForm) { MEMMAN_DELETE(getAddressForm); delete getAddressForm; } } void RedirectForm::show(t_user *user, const list &contacts) { user_config = user; int num = 0; for (list::const_iterator i = contacts.begin(); i != contacts.end(); i++, num++) { if (num == 0) contact1LineEdit->setText(i->c_str()); if (num == 1) contact2LineEdit->setText(i->c_str()); if (num == 2) contact3LineEdit->setText(i->c_str()); } QDialog::show(); } void RedirectForm::validate() { t_display_url destination; list dest_list; // 1st choice destination ui->expand_destination(user_config, contact1LineEdit->text().trimmed().toStdString(), destination); if (destination.is_valid()) { dest_list.push_back(destination); } else { contact1LineEdit->selectAll(); return; } // 2nd choice destination if (!contact2LineEdit->text().isEmpty()) { ui->expand_destination(user_config, contact2LineEdit->text().trimmed().toStdString(), destination); if (destination.is_valid()) { dest_list.push_back(destination); } else { contact2LineEdit->selectAll(); return; } } // 3rd choice destination if (!contact3LineEdit->text().isEmpty()) { ui->expand_destination(user_config, contact3LineEdit->text().trimmed().toStdString(), destination); if (destination.is_valid()) { dest_list.push_back(destination); } else { contact3LineEdit->selectAll(); return; } } emit destinations(dest_list); accept(); } void RedirectForm::showAddressBook() { if (!getAddressForm) { getAddressForm = new GetAddressForm(this); getAddressForm->setModal(true); MEMMAN_NEW(getAddressForm); } connect(getAddressForm, SIGNAL(address(const QString &)), this, SLOT(selectedAddress(const QString &))); getAddressForm->show(); } void RedirectForm::showAddressBook1() { nrAddressBook = 1; showAddressBook(); } void RedirectForm::showAddressBook2() { nrAddressBook = 2; showAddressBook(); } void RedirectForm::showAddressBook3() { nrAddressBook = 3; showAddressBook(); } void RedirectForm::selectedAddress(const QString &address) { switch(nrAddressBook) { case 1: contact1LineEdit->setText(address); break; case 2: contact2LineEdit->setText(address); break; case 3: contact3LineEdit->setText(address); break; } } twinkle-1.10.1/src/gui/redirectform.h000066400000000000000000000022041277565361200174740ustar00rootroot00000000000000#ifndef REDIRECTFORM_UI_H #define REDIRECTFORM_UI_H #include "ui_redirectform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "getaddressform.h" #include "sockets/url.h" #include "user.h" class RedirectForm : public QDialog, public Ui::RedirectForm { Q_OBJECT public: RedirectForm(QWidget* parent = 0); ~RedirectForm(); public slots: virtual void show( t_user * user, const list & contacts ); virtual void validate(); virtual void showAddressBook(); virtual void showAddressBook1(); virtual void showAddressBook2(); virtual void showAddressBook3(); virtual void selectedAddress( const QString & address ); signals: void destinations(const list &); protected slots: virtual void languageChange(); private: GetAddressForm *getAddressForm; int nrAddressBook; t_user *user_config; void init(); void destroy(); }; #endif twinkle-1.10.1/src/gui/redirectform.ui000066400000000000000000000231441277565361200176700ustar00rootroot00000000000000 RedirectForm 0 0 588 190 5 5 0 0 Twinkle - Redirect Redirect incoming call to You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. &3rd choice destination: contact3LineEdit false &2nd choice destination: contact2LineEdit false You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. &1st choice destination: contact1LineEdit false Qt::TabFocus F10 :/icons/images/kontact_contacts.png Address book Select an address from the address book. Qt::TabFocus F12 :/icons/images/kontact_contacts.png Address book Select an address from the address book. Qt::TabFocus F11 :/icons/images/kontact_contacts.png Address book Select an address from the address book. 20 16 QSizePolicy::Expanding Qt::Vertical 361 20 QSizePolicy::Expanding Qt::Horizontal &OK true &Cancel contact1LineEdit contact2LineEdit contact3LineEdit address1ToolButton address2ToolButton address3ToolButton okPushButton cancelPushButton list sockets/url.h ui_getaddressform.h user.h cancelPushButton clicked() RedirectForm reject() okPushButton clicked() RedirectForm validate() address1ToolButton clicked() RedirectForm showAddressBook1() address2ToolButton clicked() RedirectForm showAddressBook2() address3ToolButton clicked() RedirectForm showAddressBook3() twinkle-1.10.1/src/gui/selectnicform.cpp000066400000000000000000000057751277565361200202170ustar00rootroot00000000000000//Added by qt3to4: #include /* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "gui.h" #include #include "sys_settings.h" #include "selectnicform.h" /* * Constructs a SelectNicForm as a child of 'parent', with the * name 'name' and widget flags set to 'f'. * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ SelectNicForm::SelectNicForm(QWidget* parent) : QDialog(parent) { setupUi(this); init(); } /* * Destroys the object and frees any allocated resources */ SelectNicForm::~SelectNicForm() { // no need to delete child widgets, Qt does it all for us } /* * Sets the strings of the subwidgets using the current * language. */ void SelectNicForm::languageChange() { retranslateUi(this); } void SelectNicForm::init() { idxDefault = -1; } void SelectNicForm::setAsDefault(bool setIp) { #if 0 // DEPRECATED // Only show the information when the default button is // pressed for the first time. if (idxDefault == -1) { QMessageBox::information(this, PRODUCT_NAME, tr( "If you want to remove or " "change the default at a later time, you can do that " "via the system settings.")); } // Store current index as the changeItem method also changes // the current index as a side effect. int idxNewDefault = nicListBox->currentItem(); // Restore pixmap of the old default if (idxDefault != -1) { nicListBox->changeItem( QPixmap(":/icons/images/kcmpci16.png"), nicListBox->text(idxDefault), idxDefault); } // Set pixmap of the default idxDefault = idxNewDefault; nicListBox->changeItem( QPixmap(":/icons/images/twinkle16.png"), nicListBox->text(idxDefault), idxDefault); // Write default to system settings int pos = nicListBox->currentText().findRev(':'); if (setIp) { sys_config->set_start_user_host(nicListBox->currentText().mid(pos + 1).ascii()); sys_config->set_start_user_nic(""); } else { sys_config->set_start_user_nic(nicListBox->currentText().left(pos).ascii()); sys_config->set_start_user_host(""); } string error_msg; if (!sys_config->write_config(error_msg)) { // Failed to write config file ((t_gui *)ui)->cb_show_msg(this, error_msg, MSG_CRITICAL); } #endif } void SelectNicForm::setAsDefaultIp() { setAsDefault(true); } void SelectNicForm::setAsDefaultNic() { setAsDefault(false); } twinkle-1.10.1/src/gui/selectnicform.h000066400000000000000000000007241277565361200176510ustar00rootroot00000000000000#ifndef SELECTNICFORM_H #define SELECTNICFORM_H #include #include "ui_selectnicform.h" class SelectNicForm : public QDialog, public Ui::SelectNicForm { Q_OBJECT public: SelectNicForm(QWidget* parent = 0); ~SelectNicForm(); public slots: virtual void setAsDefault( bool setIp ); virtual void setAsDefaultIp(); virtual void setAsDefaultNic(); protected slots: virtual void languageChange(); private: int idxDefault; void init(); }; #endif twinkle-1.10.1/src/gui/selectnicform.ui000066400000000000000000000134641277565361200200440ustar00rootroot00000000000000 SelectNicForm 0 0 482 144 Twinkle - Select NIC :/icons/images/kcmpci.png false 20 41 QSizePolicy::Expanding Qt::Vertical Select the network interface/IP address that you want to use: Qt::AlignTop true You have multiple IP addresses. Here you must select which IP address should be used. This IP address will be used inside the SIP messages. 20 16 QSizePolicy::Expanding Qt::Vertical 40 20 QSizePolicy::Expanding Qt::Horizontal Set as default &IP Alt+I Make the selected IP address the default IP address. The next time you start Twinkle, this IP address will be automatically selected. Set as default &NIC Alt+N Make the selected network interface the default interface. The next time you start Twinkle, this interface will be automatically selected. &OK Alt+O true okPushButton clicked() SelectNicForm accept() nicListBox selected(QString) SelectNicForm accept() defaultIpPushButton clicked() SelectNicForm setAsDefaultIp() defaultNicPushButton clicked() SelectNicForm setAsDefaultNic() twinkle-1.10.1/src/gui/selectprofileform.cpp000066400000000000000000000467201277565361200211010ustar00rootroot00000000000000//Added by qt3to4: #include /* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include "user.h" #include #include #include "protocol.h" #include "gui.h" #include "userprofileform.h" #include "getprofilenameform.h" #include "audits/memman.h" #include "wizardform.h" #include "syssettingsform.h" #include #include "service.h" #include "presence/buddy.h" #include "diamondcardprofileform.h" #include "selectprofileform.h" /* * Constructs a SelectProfileForm as a child of 'parent', with the * name 'name' and widget flags set to 'f'. * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ SelectProfileForm::SelectProfileForm(QWidget* parent) : QDialog(parent) { setupUi(this); init(); } /* * Destroys the object and frees any allocated resources */ SelectProfileForm::~SelectProfileForm() { destroy(); // no need to delete child widgets, Qt does it all for us } /* * Sets the strings of the subwidgets using the current * language. */ void SelectProfileForm::languageChange() { retranslateUi(this); } void SelectProfileForm::init() { user_config = 0; #ifndef WITH_DIAMONDCARD diamondcardPushButton->hide(); #endif } void SelectProfileForm::destroy() { if (user_config) { MEMMAN_DELETE(user_config); delete user_config; } } // The exec() method is called at startup int SelectProfileForm::execForm() { mainWindow = 0; profileListView->clear(); defaultSet = false; // no default has been set // Get list of all profiles QStringList profiles; QString error; if (!SelectProfileForm::getUserProfiles(profiles, error)) { QMessageBox::critical(this, PRODUCT_NAME, error); return QDialog::Rejected; } // If there are no profiles then the user has to create one if (profiles.isEmpty()) { QMessageBox::information(this, PRODUCT_NAME, tr( ""\ "Before you can use Twinkle, you must create a user "\ "profile.
Click OK to create a profile.")); int newProfileMethod = QMessageBox::question(this, PRODUCT_NAME, tr( ""\ "You can use the profile editor to create a profile. "\ "With the profile editor you can change many settings "\ "to tune the SIP protocol, RTP and many other things.

"\ "Alternatively you can use the wizard to quickly setup a "\ "user profile. The wizard asks you only a few essential "\ "settings. If you create a user profile with the wizard you "\ "can still edit the full profile with the profile editor at a later "\ "time.

") #ifdef WITH_DIAMONDCARD + tr ("You can create a Diamondcard account to make worldwide "\ "calls to regular and cell phones and send SMS messages.

") #endif + tr("Choose what method you wish to use."), tr("&Wizard"), tr("&Profile editor") #ifdef WITH_DIAMONDCARD , tr("&Diamondcard") #endif ); switch (newProfileMethod) { case 0: wizardProfile(true); break; case 1: newProfile(true); break; case 2: diamondcardProfile(true); break; default: return QDialog::Rejected; } if (profileListView->count() == 0) { // No profile has been created. return QDialog::Rejected; } // Select the created profile QListWidgetItem* item = profileListView->currentItem(); QString profile = item->text(); profile.append(USER_FILE_EXT); selectedProfiles.clear(); selectedProfiles.push_back(profile.toStdString()); QMessageBox::information(this, PRODUCT_NAME, tr( ""\ "Next you may adjust the system settings. "\ "You can change these settings always at a later time."\ "

"\ "Click OK to view and adjust the system settings.")); SysSettingsForm f(this); f.setModal(true); f.exec(); return QDialog::Accepted; } fillProfileListView(profiles); sysPushButton->show(); runPushButton->setFocus(); // Show the modal dialog return QDialog::exec(); } // The showForm() method is called from File menu when Twinkle is running. // The execForm() method cannot be used as it will block the Qt event loop. // NOTE: the method show() is not re-implemented as Qt calls this method // from exec() internally. void SelectProfileForm::showForm(QWidget *_mainWindow) { mainWindow = _mainWindow; profileListView->clear(); defaultSet = false; // Get list of all profiles QStringList profiles; QString error; if (!SelectProfileForm::getUserProfiles(profiles, error)) { QMessageBox::critical(this, PRODUCT_NAME, error); return; } // Initialize profile list view fillProfileListView(profiles); for (int i = 0; i < profileListView->count(); i++) { QListWidgetItem* item = profileListView->item(i); QString profile = item->text(); // Set pixmap of default profile list l = sys_config->get_start_user_profiles(); if (std::find(l.begin(), l.end(), profile.toStdString()) != l.end()) { item->setData(Qt::DecorationRole, QPixmap(":/icons/images/twinkle16.png")); defaultSet = true; } // Tick check box of active profile item->setFlags(item->flags() | Qt::ItemIsUserCheckable); if (phone->ref_user_profile(profile.toStdString())) { item->setCheckState(Qt::Checked); } } sysPushButton->hide(); runPushButton->setText("&OK"); runPushButton->setFocus(); QDialog::show(); } void SelectProfileForm::runProfile() { selectedProfiles.clear(); for (int i = 0; i < profileListView->count(); i++) { QListWidgetItem* item = profileListView->item(i); if (item->checkState() == Qt::Checked) { QString profile = item->text(); profile.append(USER_FILE_EXT); selectedProfiles.push_back(profile.toStdString()); } } if (selectedProfiles.empty()) { QMessageBox::warning(this, PRODUCT_NAME, tr( "You did not select any user profile to run.\n"\ "Please select a profile.")); return; } // This signal will be caught when Twinkle is running. // At startup the selectedProfiles attribute is read. emit selection(selectedProfiles); accept(); } void SelectProfileForm::editProfile() { QListWidgetItem *item = profileListView->currentItem(); QString profile = item->text(); // If the profile to edit is currently active, then edit the in-memory // user profile owned by the t_phone_user object if (mainWindow) { t_user *active_user = phone->ref_user_profile(profile.toStdString()); if (active_user) { list user_list; user_list.push_back(active_user); UserProfileForm *f = new UserProfileForm(this); f->setAttribute(Qt::WA_DeleteOnClose); f->setModal(true); connect(f, SIGNAL(authCredentialsChanged(t_user *, const string&)), mainWindow, SLOT(updateAuthCache(t_user *, const string&))); connect(f, SIGNAL(stunServerChanged(t_user *)), mainWindow, SLOT(updateStunSettings(t_user *))); f->show(user_list, ""); return; } } // Edit the user profile from disk. profile.append(USER_FILE_EXT); // Read selected config file string error_msg; if (user_config) { MEMMAN_DELETE(user_config); delete user_config; } user_config = new t_user(); MEMMAN_NEW(user_config); if (!user_config->read_config(profile.toStdString(), error_msg)) { ((t_gui *)ui)->cb_show_msg(this, error_msg, MSG_WARNING); return; } // Show the edit user profile form (modal dialog) list user_list; user_list.push_back(user_config); UserProfileForm *f = new UserProfileForm(this); f->setAttribute(Qt::WA_DeleteOnClose); f->setModal(true); f->show(user_list, ""); } void SelectProfileForm::newProfile() { newProfile(false); } void SelectProfileForm::newProfile(bool exec_mode) { // Ask user for a profile name GetProfileNameForm getProfileNameForm(this); getProfileNameForm.setModal(true); if (!getProfileNameForm.execNewName()) return; // Create file name QString profile = getProfileNameForm.getProfileName(); QString filename = profile; filename.append(USER_FILE_EXT); // Create a new user config if (user_config) { MEMMAN_DELETE(user_config); delete user_config; } user_config = new t_user(); MEMMAN_NEW(user_config); user_config->set_config(filename.toStdString()); // Show the edit user profile form (modal dialog) list user_list; user_list.push_back(user_config); UserProfileForm *f = new UserProfileForm(this); f->setAttribute(Qt::WA_DeleteOnClose); f->setModal(true); connect(f, SIGNAL(success()), this, SLOT(newProfileCreated())); if (exec_mode) { f->exec(user_list, ""); } else { f->show(user_list, ""); } } void SelectProfileForm::newProfileCreated() { // New profile created // Add the new profile to the profile list box QListWidgetItem* item = new QListWidgetItem(QPixmap(":/icons/images/penguin-small.png"), QString::fromStdString(user_config->get_profile_name()), profileListView); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(Qt::Unchecked); // Make the new profile the selected profile // Do not change this without changing the exec method. // When there are no profiles, the exec methods relies on the // fact that afer creation of the profile it is selected. profileListView->setCurrentItem(item); // Enable buttons that act on a profile editPushButton->setEnabled(true); deletePushButton->setEnabled(true); renamePushButton->setEnabled(true); defaultPushButton->setEnabled(true); runPushButton->setEnabled(true); } void SelectProfileForm::deleteProfile() { QListWidgetItem *item = profileListView->currentItem(); QString profile = item->text(); QString msg = tr("Are you sure you want to delete profile '%1'?").arg(profile); QMessageBox *mb = new QMessageBox(tr("Delete profile"), msg, QMessageBox::Warning, QMessageBox::Yes, QMessageBox::No, QMessageBox::NoButton, this); MEMMAN_NEW(mb); if (mb->exec() == QMessageBox::Yes) { // Delete file QDir d = QDir::home(); d.cd(USER_DIR); QString filename = profile; filename.append(USER_FILE_EXT); QString fullname = d.filePath(filename); if (!QFile::remove(fullname)) { // Failed to delete file QMessageBox::critical(this, PRODUCT_NAME, tr("Failed to delete profile.")); } else { // Delete possible backup of the profile QString backupname = fullname; backupname.append("~"); (void)QFile::remove(backupname); // Delete service files filename = profile; filename.append(SVC_FILE_EXT); fullname = d.filePath(filename); (void)QFile::remove(fullname); fullname.append("~"); (void)QFile::remove(fullname); // Delete profile from list of default profiles in // system settings list l = sys_config->get_start_user_profiles(); if (std::find(l.begin(), l.end(), profile.toStdString()) != l.end()) { l.remove(profile.toStdString()); sys_config->set_start_user_profiles(l); string error_msg; if (!sys_config->write_config(error_msg)) { // Failed to write config file ((t_gui *)ui)->cb_show_msg(this, error_msg, MSG_CRITICAL); } } // Delete profile from profile list box QListWidgetItem *item = profileListView-> currentItem(); delete item; if (profileListView->count() == 0) { // There are no profiles anymore // Disable buttons that act on a profile editPushButton->setEnabled(false); deletePushButton->setEnabled(false); renamePushButton->setEnabled(false); defaultPushButton->setEnabled(false); runPushButton->setEnabled(false); } else { profileListView->setCurrentItem(profileListView->item(0)); } } } MEMMAN_DELETE(mb); delete mb; } void SelectProfileForm::renameProfile() { QListWidgetItem *item = profileListView->currentItem(); QString oldProfile = item->text(); // Ask user for a new profile name GetProfileNameForm getProfileNameForm(this); getProfileNameForm.setModal(true); if (!getProfileNameForm.execRename(oldProfile)) return; // Create file name for the new profile QString newProfile = getProfileNameForm.getProfileName(); QString newFilename = newProfile; newFilename.append(USER_FILE_EXT); // Create file name for the old profile QString oldFilename = oldProfile; oldFilename.append(USER_FILE_EXT); // Rename the file QDir d = QDir::home(); d.cd(USER_DIR); if (!d.rename(oldFilename, newFilename)) { // Failed to delete file QMessageBox::critical(this, PRODUCT_NAME, tr("Failed to rename profile.")); } else { // If there is a backup of the profile, rename it too. QString oldBackupFilename = oldFilename; oldBackupFilename.append("~"); QString oldBackupFullname = d.filePath(oldBackupFilename); if (QFile::exists(oldBackupFullname)) { QString newBackupFilename = newFilename; newBackupFilename.append("~"); d.rename(oldBackupFilename, newBackupFilename); } // Rename service files oldFilename = oldProfile; oldFilename.append(SVC_FILE_EXT); QString oldFullname = d.filePath(oldFilename); if (QFile::exists(oldFullname)) { newFilename = newProfile; newFilename.append(SVC_FILE_EXT); d.rename(oldFilename, newFilename); } // Rename service backup file oldFilename.append("~"); oldFullname = d.filePath(oldFilename); if (QFile::exists(oldFullname)) { newFilename.append("~"); d.rename(oldFilename, newFilename); } // Rename buddy list file oldFilename = oldProfile; oldFilename.append(BUDDY_FILE_EXT); oldFullname = d.filePath(oldFilename); if (QFile::exists(oldFullname)) { newFilename = newProfile; newFilename.append(BUDDY_FILE_EXT); d.rename(oldFilename, newFilename); } // Rename profile in list of default profiles in // system settings list l = sys_config->get_start_user_profiles(); if (std::find(l.begin(), l.end(), oldProfile.toStdString()) != l.end()) { std::replace(l.begin(), l.end(), oldProfile.toStdString(), newProfile.toStdString()); sys_config->set_start_user_profiles(l); string error_msg; if (!sys_config->write_config(error_msg)) { // Failed to write config file ((t_gui *)ui)->cb_show_msg(this, error_msg, MSG_CRITICAL); } } emit profileRenamed(); // Change profile name in the list box QListWidgetItem *item = profileListView->currentItem(); item->setText(newProfile); } } void SelectProfileForm::setAsDefault() { // Only show the information when the default button is // pressed for the first time. if (!defaultSet) { QMessageBox::information(this, PRODUCT_NAME, tr( "

" "If you want to remove or " "change the default at a later time, you can do that " "via the system settings." "

")); } defaultSet = true; // Restore all pixmaps // Set pixmap of the default profiles. // Set default profiles in system settings. list l; for (int i = 0; i < profileListView->count(); i++) { QListWidgetItem* item = profileListView->item(i); if (item->checkState() == Qt::Checked) { item->setData(Qt::DecorationRole, QPixmap(":/icons/images/twinkle16.png")); l.push_back(item->text().toStdString()); } else { item->setData(Qt::DecorationRole, QPixmap(":/icons/images/penguin-small.png")); } } sys_config->set_start_user_profiles(l); // Write default to system settings string error_msg; if (!sys_config->write_config(error_msg)) { // Failed to write config file ((t_gui *)ui)->cb_show_msg(this, error_msg, MSG_CRITICAL); } } void SelectProfileForm::wizardProfile() { wizardProfile(false); } void SelectProfileForm::wizardProfile(bool exec_mode) { // Ask user for a profile name GetProfileNameForm getProfileNameForm(this); getProfileNameForm.setModal(true); if (!getProfileNameForm.execNewName()) return; // Create file name QString profile = getProfileNameForm.getProfileName(); QString filename = profile; filename.append(USER_FILE_EXT); // Create a new user config if (user_config) { MEMMAN_DELETE(user_config); delete user_config; } user_config = new t_user(); MEMMAN_NEW(user_config); user_config->set_config(filename.toStdString()); // Show the wizard form (modal dialog) WizardForm *f = new WizardForm(this); f->setAttribute(Qt::WA_DeleteOnClose); f->setModal(true); connect(f, SIGNAL(success()), this, SLOT(newProfileCreated())); if (exec_mode) { f->exec(user_config); } else { f->show(user_config); } } void SelectProfileForm::diamondcardProfile() { diamondcardProfile(false); } void SelectProfileForm::diamondcardProfile(bool exec_mode) { // Create a new user config if (user_config) { MEMMAN_DELETE(user_config); delete user_config; } user_config = new t_user(); MEMMAN_NEW(user_config); // Show the diamondcard profile form (modal dialog) DiamondcardProfileForm *f = new DiamondcardProfileForm(this); f->setAttribute(Qt::WA_DeleteOnClose); f->setModal(true); connect(f, SIGNAL(success()), this, SLOT(newProfileCreated())); if (exec_mode) { f->exec(user_config); } else { f->show(user_config); } } void SelectProfileForm::sysSettings() { SysSettingsForm *f = new SysSettingsForm(this); f->setAttribute(Qt::WA_DeleteOnClose); f->setModal(true); f->show(); } // Get a list of all profiles. Returns false if there is an error. bool SelectProfileForm::getUserProfiles(QStringList &profiles, QString &error) { // Find the .twinkle directory in HOME QDir d = QDir::home(); if (!d.cd(USER_DIR)) { error = tr("Cannot find .twinkle directory in your home directory."); return false; } // Select all config files QString filterName = "*"; filterName.append(USER_FILE_EXT); d.setFilter(QDir::Files); d.setNameFilters(QStringList(filterName)); d.setSorting(QDir::Name | QDir::IgnoreCase); profiles = d.entryList(); return true; } void SelectProfileForm::fillProfileListView(const QStringList &profiles) { // Put the profiles in the profile list view for (QStringList::ConstIterator i = profiles.begin(); i != profiles.end(); i++) { // Strip off the user file extension QString profile = *i; profile.truncate(profile.length() - strlen(USER_FILE_EXT)); QListWidgetItem* item = new QListWidgetItem(QPixmap(":/icons/images/penguin-small.png"), profile, profileListView); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(Qt::Unchecked); } // Highlight the first profile profileListView->setCurrentItem(profileListView->item(0)); } void SelectProfileForm::toggleItem(QModelIndex index) { QListWidgetItem* item = profileListView->item(index.row()); Qt::CheckState state = item->checkState(); if (state == Qt::Checked) item->setCheckState(Qt::Unchecked); else item->setCheckState(Qt::Checked); } twinkle-1.10.1/src/gui/selectprofileform.h000066400000000000000000000025361277565361200205430ustar00rootroot00000000000000#ifndef SELECTPROFILEFORM_H #define SELECTPROFILEFORM_H class t_phone; extern t_phone *phone; #include #include #include "phone.h" #include #include "ui_selectprofileform.h" class SelectProfileForm : public QDialog, public Ui::SelectProfileForm { Q_OBJECT public: SelectProfileForm(QWidget* parent = 0); ~SelectProfileForm(); std::list selectedProfiles; virtual int execForm(); static bool getUserProfiles( QStringList & profiles, QString & error ); public slots: virtual void showForm( QWidget * _mainWindow ); virtual void runProfile(); virtual void editProfile(); virtual void newProfile(); virtual void newProfile( bool exec_mode ); virtual void newProfileCreated(); virtual void deleteProfile(); virtual void renameProfile(); virtual void setAsDefault(); virtual void wizardProfile(); virtual void wizardProfile( bool exec_mode ); virtual void diamondcardProfile(); virtual void diamondcardProfile( bool exec_mode ); virtual void sysSettings(); virtual void fillProfileListView( const QStringList & profiles ); virtual void toggleItem( QModelIndex item ); signals: void selection(const list &); void profileRenamed(); protected slots: virtual void languageChange(); private: bool defaultSet; t_user *user_config; QWidget *mainWindow; void init(); void destroy(); }; #endif twinkle-1.10.1/src/gui/selectprofileform.ui000066400000000000000000000262071277565361200207320ustar00rootroot00000000000000 SelectProfileForm 0 0 499 511 Twinkle - Select user profile 0 0 Select user profile(s) to run: Qt::AlignVCenter true Qt::Vertical QSizePolicy::Expanding 20 20 Create profile Create a new profile with the profile editor. Ed&itor Alt+I Create a new profile with the wizard. &Wizard Alt+W Create a profile for a Diamondcard account. With a Diamondcard account you can make worldwide calls to regular and cell phones and send SMS messages. Dia&mondcard Alt+M Modify profile Edit the highlighted profile. &Edit Alt+E Delete the highlighted profile. &Delete Alt+D Rename the highlighted profile. Ren&ame Alt+A Startup profile Make the selected profiles the default profiles. The next time you start Twinkle, these profiles will be automatically run. &Set as default Alt+S Run Twinkle with the selected profiles. &Run Alt+R true Edit the system settings. S&ystem settings Alt+Y &Cancel Alt+C Tick the check boxes of the user profiles that you want to run and press run. profileListView newPushButton wizardPushButton diamondcardPushButton editPushButton deletePushButton renamePushButton defaultPushButton runPushButton sysPushButton cancelPushButton list string phone.h cancelPushButton clicked() SelectProfileForm reject() 392 501 20 20 runPushButton clicked() SelectProfileForm runProfile() 400 435 20 20 editPushButton clicked() SelectProfileForm editProfile() 400 289 20 20 newPushButton clicked() SelectProfileForm newProfile() 400 172 20 20 deletePushButton clicked() SelectProfileForm deleteProfile() 400 318 20 20 renamePushButton clicked() SelectProfileForm renameProfile() 400 347 20 20 wizardPushButton clicked() SelectProfileForm wizardProfile() 400 201 20 20 defaultPushButton clicked() SelectProfileForm setAsDefault() 400 406 20 20 sysPushButton clicked() SelectProfileForm sysSettings() 392 472 20 20 diamondcardPushButton clicked() SelectProfileForm diamondcardProfile() 400 230 20 20 profileListView doubleClicked(QModelIndex) SelectProfileForm toggleItem(QModelIndex) 228 131 433 27 toggleItem(QModelIndex) twinkle-1.10.1/src/gui/selectuserform.cpp000066400000000000000000000105621277565361200204120ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include #include "selectuserform.h" /* * Constructs a SelectUserForm as a child of 'parent', with the * name 'name' and widget flags set to 'f'. * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ SelectUserForm::SelectUserForm(QWidget* parent) : QDialog(parent) { setupUi(this); init(); } /* * Destroys the object and frees any allocated resources */ SelectUserForm::~SelectUserForm() { // no need to delete child widgets, Qt does it all for us } /* * Sets the strings of the subwidgets using the current * language. */ void SelectUserForm::languageChange() { retranslateUi(this); } void SelectUserForm::init() { } void SelectUserForm::show(t_select_purpose purpose) { QString title, msg_purpose; // Set dialog caption and purpose title = PRODUCT_NAME; title += " - "; switch (purpose) { case SELECT_REGISTER: title.append(tr("Register")); msg_purpose = tr("Select users that you want to register."); break; case SELECT_DEREGISTER: title.append(tr("Deregister")); msg_purpose = tr("Select users that you want to deregister."); break; case SELECT_DEREGISTER_ALL: title.append(tr("Deregister all devices")); msg_purpose = tr("Select users for which you want to deregister all devices."); break; case SELECT_DND: title.append(tr("Do not disturb")); msg_purpose = tr("Select users for which you want to enable 'do not disturb'."); break; case SELECT_AUTO_ANSWER: title.append(tr("Auto answer")); msg_purpose = tr("Select users for which you want to enable 'auto answer'."); break; default: assert(false); } setWindowTitle(title); purposeTextLabel->setText(msg_purpose); // Fill list view list user_list = phone->ref_users(); for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { QListWidgetItem* item = new QListWidgetItem(QString::fromStdString((*i)->get_profile_name()), userListView); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(Qt::Unchecked); switch (purpose) { case SELECT_DND: if (phone->ref_service(*i)->is_dnd_active()) item->setCheckState(Qt::Checked); break; case SELECT_AUTO_ANSWER: if (phone->ref_service(*i)->is_auto_answer_active()) item->setCheckState(Qt::Checked); break; default: break; } } QDialog::show(); } void SelectUserForm::validate() { list selected_list, not_selected_list; for (int i = 0; i < userListView->count(); i++) { QListWidgetItem *item = userListView->item(i); if (item->checkState() == Qt::Checked) { selected_list.push_back(phone-> ref_user_profile(item->text().toStdString())); } else { not_selected_list.push_back(phone-> ref_user_profile(item->text().toStdString())); } } emit (selection(selected_list)); emit (not_selected(not_selected_list)); accept(); } void SelectUserForm::selectAll() { for (int i = 0; i < userListView->count(); i++) { QListWidgetItem *item = userListView->item(i); item->setCheckState(Qt::Checked); } } void SelectUserForm::clearAll() { for (int i = 0; i < userListView->count(); i++) { QListWidgetItem *item = userListView->item(i); item->setCheckState(Qt::Unchecked); } } void SelectUserForm::toggle(QModelIndex index) { QListWidgetItem *item = userListView->item(index.row()); Qt::CheckState state = item->checkState(); item->setCheckState((state == Qt::Checked) ? Qt::Unchecked : Qt::Checked); } twinkle-1.10.1/src/gui/selectuserform.h000066400000000000000000000012421277565361200200520ustar00rootroot00000000000000#ifndef SELECTUSERFORM_H #define SELECTUSERFORM_H class t_phone; extern t_phone *phone; #include "gui.h" #include "phone.h" #include "user.h" #include "ui_selectuserform.h" class SelectUserForm : public QDialog, public Ui::SelectUserForm { Q_OBJECT public: SelectUserForm(QWidget* parent = 0); ~SelectUserForm(); public slots: virtual void show( t_select_purpose purpose ); virtual void validate(); virtual void selectAll(); virtual void clearAll(); virtual void toggle( QModelIndex item ); signals: void selection(list); void not_selected(list); protected slots: virtual void languageChange(); private: void init(); }; #endif twinkle-1.10.1/src/gui/selectuserform.ui000066400000000000000000000114721277565361200202460ustar00rootroot00000000000000 SelectUserForm 0 0 582 226 Twinkle - Select user &Cancel Alt+C &Select all Alt+S Qt::Horizontal QSizePolicy::Expanding 41 20 &OK Alt+O true C&lear all Alt+L 0 0 purpose false QAbstractItemView::NoSelection userListView selectPushButton clearPushButton okPushButton cancelPushButton user.h phone.h gui.h okPushButton clicked() SelectUserForm validate() 20 20 20 20 cancelPushButton clicked() SelectUserForm reject() 20 20 20 20 selectPushButton clicked() SelectUserForm selectAll() 20 20 20 20 clearPushButton clicked() SelectUserForm clearAll() 20 20 20 20 userListView doubleClicked(QModelIndex) SelectUserForm toggle(QModelIndex) 20 20 20 20 twinkle-1.10.1/src/gui/sendfileform.cpp000066400000000000000000000062161277565361200200260ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifdef HAVE_KDE #include #endif #include "audits/memman.h" #include "gui.h" #include #include #include "sendfileform.h" /* * Constructs a SendFileForm as a child of 'parent', with the * name 'name' and widget flags set to 'f'. * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ SendFileForm::SendFileForm(QWidget* parent) : QDialog(parent) { setupUi(this); init(); } /* * Destroys the object and frees any allocated resources */ SendFileForm::~SendFileForm() { destroy(); // no need to delete child widgets, Qt does it all for us } /* * Sets the strings of the subwidgets using the current * language. */ void SendFileForm::languageChange() { retranslateUi(this); } void SendFileForm::init() { setAttribute(Qt::WA_DeleteOnClose); _chooseFileDialog = NULL; } void SendFileForm::destroy() { // Auto destruct window MEMMAN_DELETE(this); if (_chooseFileDialog) { MEMMAN_DELETE(_chooseFileDialog); delete _chooseFileDialog; } } /** Signal the selected information to an observer. */ void SendFileForm::signalSelectedInfo() { if (!QFile::exists(fileLineEdit->text())) { ((t_gui *)ui)->cb_show_msg(this, tr("File does not exist.").toStdString(), MSG_WARNING); return; } emit selected(fileLineEdit->text(), subjectLineEdit->text()); accept(); } /** Choose a file from a file dialog. */ void SendFileForm::chooseFile() { #ifdef HAVE_KDE KFileDialog *d = new KFileDialog(QString::null, QString::null, this, 0, true); MEMMAN_NEW(d); d->setOperationMode(KFileDialog::Other); connect(d, SIGNAL(okClicked()), this, SLOT(setFilename())); #else QFileDialog *d = new QFileDialog(this); MEMMAN_NEW(d); connect(d, SIGNAL(fileSelected(const QString &)), this, SLOT(setFilename())); #endif d->setWindowTitle(tr("Send file...")); if (_chooseFileDialog) { MEMMAN_DELETE(_chooseFileDialog); delete _chooseFileDialog; } _chooseFileDialog = d; d->show(); } /** * Set the filename value. * @param filename [in] The value to set. */ void SendFileForm::setFilename() { QString filename; #ifdef HAVE_KDE KFileDialog *d = dynamic_cast(_chooseFileDialog); filename = d->selectedFile(); #else QFileDialog *d = dynamic_cast(_chooseFileDialog); QStringList files = d->selectedFiles(); if (!files.empty()) filename = files[0]; #endif fileLineEdit->setText(filename); } twinkle-1.10.1/src/gui/sendfileform.h000066400000000000000000000010241277565361200174630ustar00rootroot00000000000000#ifndef SENDFILEFORM_H #define SENDFILEFORM_H #include "ui_sendfileform.h" class SendFileForm : public QDialog, public Ui::SendFileForm { Q_OBJECT public: SendFileForm(QWidget* parent = 0); ~SendFileForm(); public slots: virtual void signalSelectedInfo(); virtual void chooseFile(); virtual void setFilename(); signals: void selected(const QString &filename, const QString &subject); protected slots: virtual void languageChange(); private: QDialog *_chooseFileDialog; void init(); void destroy(); }; #endif twinkle-1.10.1/src/gui/sendfileform.ui000066400000000000000000000126461277565361200176650ustar00rootroot00000000000000 SendFileForm 0 0 461 127 Twinkle - Send File Qt::TabFocus :/icons/images/fileopen.png Select file to send. 28 20 QSizePolicy::Minimum Qt::Horizontal &File: fileLineEdit false &Subject: subjectLineEdit false 20 16 QSizePolicy::Expanding Qt::Vertical 141 20 QSizePolicy::Expanding Qt::Horizontal &OK Alt+O &Cancel Alt+C subjectLineEdit fileLineEdit okPushButton cancelPushButton qstring.h cancelPushButton clicked() SendFileForm reject() okPushButton clicked() SendFileForm signalSelectedInfo() fileToolButton clicked() SendFileForm chooseFile() twinkle-1.10.1/src/gui/srvredirectform.cpp000066400000000000000000000234461277565361200205750ustar00rootroot00000000000000//Added by qt3to4: #include /* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "phone.h" #include #include #include "gui.h" #include "audits/memman.h" #include "srvredirectform.h" /* * Constructs a SrvRedirectForm as a child of 'parent', with the * name 'name' and widget flags set to 'f'. * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ SrvRedirectForm::SrvRedirectForm(QWidget* parent) : QDialog(parent) { setupUi(this); init(); } /* * Destroys the object and frees any allocated resources */ SrvRedirectForm::~SrvRedirectForm() { destroy(); // no need to delete child widgets, Qt does it all for us } /* * Sets the strings of the subwidgets using the current * language. */ void SrvRedirectForm::languageChange() { retranslateUi(this); } void SrvRedirectForm::init() { cfAlwaysGroupBox->setEnabled(false); cfBusyGroupBox->setEnabled(false); cfNoanswerGroupBox->setEnabled(false); // Keeps track of which address book tool button is clicked. nrAddressBook = 0; getAddressForm = 0; // Set toolbutton icons for disabled options. QIcon i; i = addrAlways1ToolButton->icon(); i.addPixmap(QPixmap(":/icons/images/kontact_contacts-disabled.png"), QIcon::Disabled); addrAlways1ToolButton->setIcon(i); addrAlways2ToolButton->setIcon(i); addrAlways3ToolButton->setIcon(i); addrBusy1ToolButton->setIcon(i); addrBusy2ToolButton->setIcon(i); addrBusy3ToolButton->setIcon(i); addrNoanswer1ToolButton->setIcon(i); addrNoanswer2ToolButton->setIcon(i); addrNoanswer3ToolButton->setIcon(i); } void SrvRedirectForm::destroy() { if (getAddressForm) { MEMMAN_DELETE(getAddressForm); delete getAddressForm; } } void SrvRedirectForm::show() { current_user_idx = -1; ((t_gui *)ui)->fill_user_combo(userComboBox); userComboBox->setEnabled(userComboBox->count() > 1); current_user = phone->ref_users().front(); current_user_idx = 0; populate(); QDialog::show(); } void SrvRedirectForm::populate() { t_service *srv = phone->ref_service(current_user); bool cf_active; list dest_list; int field; // Call forwarding unconditional cf_active = srv->get_cf_active(CF_ALWAYS, dest_list); cfAlwaysDst1LineEdit->clear(); cfAlwaysDst2LineEdit->clear(); cfAlwaysDst3LineEdit->clear(); cfAlwaysCheckBox->setChecked(cf_active); if (cf_active) { field = 1; for (list::iterator i = dest_list.begin(); i != dest_list.end(); i++) { if (field == 1) cfAlwaysDst1LineEdit->setText(i->encode().c_str()); if (field == 2) cfAlwaysDst2LineEdit->setText(i->encode().c_str()); if (field == 3) cfAlwaysDst3LineEdit->setText(i->encode().c_str()); field++; } } // Call forwarding busy cf_active = srv->get_cf_active(CF_BUSY, dest_list); cfBusyDst1LineEdit->clear(); cfBusyDst2LineEdit->clear(); cfBusyDst3LineEdit->clear(); cfBusyCheckBox->setChecked(cf_active); if (cf_active) { field = 1; for (list::iterator i = dest_list.begin(); i != dest_list.end(); i++) { if (field == 1) cfBusyDst1LineEdit->setText(i->encode().c_str()); if (field == 2) cfBusyDst2LineEdit->setText(i->encode().c_str()); if (field == 3) cfBusyDst3LineEdit->setText(i->encode().c_str()); field++; } } // Call forwarding no answer cf_active = srv->get_cf_active(CF_NOANSWER, dest_list); cfNoanswerDst1LineEdit->clear(); cfNoanswerDst2LineEdit->clear(); cfNoanswerDst3LineEdit->clear(); cfNoanswerCheckBox->setChecked(cf_active); if (cf_active) { field = 1; for (list::iterator i = dest_list.begin(); i != dest_list.end(); i++) { if (field == 1) cfNoanswerDst1LineEdit->setText(i->encode().c_str()); if (field == 2) cfNoanswerDst2LineEdit->setText(i->encode().c_str()); if (field == 3) cfNoanswerDst3LineEdit->setText(i->encode().c_str()); field++; } } } void SrvRedirectForm::validate() { if (validateValues()) { accept(); } else { ((t_gui *)ui)->cb_show_msg(this, tr("You have entered an invalid destination.").toStdString(), MSG_WARNING); } } bool SrvRedirectForm::validateValues() { list cfDestAlways, cfDestBusy, cfDestNoanswer; bool valid = false; // Redirect unconditional valid = validate(cfAlwaysCheckBox->isChecked(), cfAlwaysDst1LineEdit, cfAlwaysDst2LineEdit, cfAlwaysDst3LineEdit, cfDestAlways); if (!valid) { cfTabWidget->setCurrentIndex(0); return false; } // Redirect busy valid = validate(cfBusyCheckBox->isChecked(), cfBusyDst1LineEdit, cfBusyDst2LineEdit, cfBusyDst3LineEdit, cfDestBusy); if (!valid) { cfTabWidget->setCurrentIndex(1); return false; } // Redirect no answer valid = validate(cfNoanswerCheckBox->isChecked(), cfNoanswerDst1LineEdit, cfNoanswerDst2LineEdit, cfNoanswerDst3LineEdit, cfDestNoanswer); if (!valid) { cfTabWidget->setCurrentIndex(2); return false; } emit destinations(current_user, cfDestAlways, cfDestBusy, cfDestNoanswer); return true; } // Validate 3 destinations if cf_active is true. // Returns true when all destinations are valid (first must be set, others may be empty) // dest_list containst the encoded destinations when valid. // If cf_active is false then the 3 destinations will be cleared. bool SrvRedirectForm::validate(bool cf_active, QLineEdit *dst1, QLineEdit *dst2, QLineEdit *dst3, list &dest_list) { t_display_url destination; dest_list.clear(); if (!cf_active) { dst1->clear(); dst2->clear(); dst3->clear(); return true; } // 1st choice destination ui->expand_destination(current_user, dst1->text().trimmed().toStdString(), destination); if (destination.is_valid()) { dest_list.push_back(destination); } else { dst1->selectAll(); return false; } // 2nd choice destination if (!dst2->text().isEmpty()) { ui->expand_destination(current_user, dst2->text().trimmed().toStdString(), destination); if (destination.is_valid()) { dest_list.push_back(destination); } else { dst2->selectAll(); return false; } } // 3rd choice destination if (!dst3->text().isEmpty()) { ui->expand_destination(current_user, dst3->text().trimmed().toStdString(), destination); if (destination.is_valid()) { dest_list.push_back(destination); } else { dst3->selectAll(); return false; } } return true; } void SrvRedirectForm::toggleAlways(bool on) { if (on) { cfAlwaysGroupBox->setEnabled(true); } else { cfAlwaysGroupBox->setEnabled(false); } } void SrvRedirectForm::toggleBusy(bool on) { if (on) { cfBusyGroupBox->setEnabled(true); } else { cfBusyGroupBox->setEnabled(false); } } void SrvRedirectForm::toggleNoanswer(bool on) { if (on) { cfNoanswerGroupBox->setEnabled(true); } else { cfNoanswerGroupBox->setEnabled(false); } } void SrvRedirectForm::changedUser(const QString &user_profile) { if (current_user_idx == -1) { // Initializing combo box return; } t_user *new_user = phone->ref_user_profile(user_profile.toStdString()); if (!new_user) { userComboBox->setCurrentIndex(current_user_idx); return; } if (!validateValues()) { userComboBox->setCurrentIndex(current_user_idx); ((t_gui *)ui)->cb_show_msg(this, tr("You have entered an invalid destination.").toStdString(), MSG_WARNING); return; } // Change current user current_user_idx = userComboBox->currentIndex(); current_user = new_user; populate(); } void SrvRedirectForm::showAddressBook() { if (!getAddressForm) { getAddressForm = new GetAddressForm(this); getAddressForm->setModal(true); MEMMAN_NEW(getAddressForm); } connect(getAddressForm, SIGNAL(address(const QString &)), this, SLOT(selectedAddress(const QString &))); getAddressForm->show(); } void SrvRedirectForm::showAddressBook1() { nrAddressBook = 1; showAddressBook(); } void SrvRedirectForm::showAddressBook2() { nrAddressBook = 2; showAddressBook(); } void SrvRedirectForm::showAddressBook3() { nrAddressBook = 3; showAddressBook(); } void SrvRedirectForm::showAddressBook4() { nrAddressBook = 4; showAddressBook(); } void SrvRedirectForm::showAddressBook5() { nrAddressBook = 5; showAddressBook(); } void SrvRedirectForm::showAddressBook6() { nrAddressBook = 6; showAddressBook(); } void SrvRedirectForm::showAddressBook7() { nrAddressBook = 7; showAddressBook(); } void SrvRedirectForm::showAddressBook8() { nrAddressBook = 8; showAddressBook(); } void SrvRedirectForm::showAddressBook9() { nrAddressBook = 9; showAddressBook(); } void SrvRedirectForm::selectedAddress(const QString &address) { switch(nrAddressBook) { case 1: cfAlwaysDst1LineEdit->setText(address); break; case 2: cfAlwaysDst2LineEdit->setText(address); break; case 3: cfAlwaysDst3LineEdit->setText(address); break; case 4: cfBusyDst1LineEdit->setText(address); break; case 5: cfBusyDst2LineEdit->setText(address); break; case 6: cfBusyDst3LineEdit->setText(address); break; case 7: cfNoanswerDst1LineEdit->setText(address); break; case 8: cfNoanswerDst2LineEdit->setText(address); break; case 9: cfNoanswerDst3LineEdit->setText(address); break; } } twinkle-1.10.1/src/gui/srvredirectform.h000066400000000000000000000030531277565361200202320ustar00rootroot00000000000000#ifndef SRVREDIRECTFORM_H #define SRVREDIRECTFORM_H #include #include "getaddressform.h" #include "phone.h" #include #include "sockets/url.h" #include "user.h" #include "ui_srvredirectform.h" class t_phone; extern t_phone *phone; class SrvRedirectForm : public QDialog, public Ui::SrvRedirectForm { Q_OBJECT public: SrvRedirectForm(QWidget* parent = 0); ~SrvRedirectForm(); virtual bool validateValues(); virtual bool validate( bool cf_active, QLineEdit * dst1, QLineEdit * dst2, QLineEdit * dst3, list & dest_list ); public slots: virtual void show(); virtual void populate(); virtual void validate(); virtual void toggleAlways( bool on ); virtual void toggleBusy( bool on ); virtual void toggleNoanswer( bool on ); virtual void changedUser( const QString & user_display_uri ); virtual void showAddressBook(); virtual void showAddressBook1(); virtual void showAddressBook2(); virtual void showAddressBook3(); virtual void showAddressBook4(); virtual void showAddressBook5(); virtual void showAddressBook6(); virtual void showAddressBook7(); virtual void showAddressBook8(); virtual void showAddressBook9(); virtual void selectedAddress( const QString & address ); signals: void destinations(t_user *, const list &always, const list &busy, const list &noanswer); protected slots: virtual void languageChange(); private: int nrAddressBook; GetAddressForm *getAddressForm; t_user *current_user; int current_user_idx; void init(); void destroy(); }; #endif twinkle-1.10.1/src/gui/srvredirectform.ui000066400000000000000000000752121277565361200204260ustar00rootroot00000000000000 SrvRedirectForm 0 0 648 315 Twinkle - Call Redirection User: userComboBox false 7 0 0 0 true There are 3 redirect services:<p> <b>Unconditional:</b> redirect all calls </p> <p> <b>Busy:</b> redirect a call if both lines are busy </p> <p> <b>No answer:</b> redirect a call when the no-answer timer expires </p> &Unconditional &Redirect all calls Alt+R Activate the unconditional redirection service. true Redirect to true You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. true You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. true &3rd choice destination: cfAlwaysDst3LineEdit false true You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. &2nd choice destination: cfAlwaysDst2LineEdit false &1st choice destination: cfAlwaysDst1LineEdit false Qt::TabFocus F10 :/icons/images/kontact_contacts.png Address book Qt::TabFocus F11 :/icons/images/kontact_contacts.png Address book Select an address from the address book. Qt::TabFocus F12 :/icons/images/kontact_contacts.png Address book Select an address from the address book. &Busy &Redirect calls when I am busy Alt+R Activate the redirection when busy service. true Redirect to true You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. &3rd choice destination: cfAlwaysDst3LineEdit false true You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. &2nd choice destination: cfAlwaysDst2LineEdit false &1st choice destination: cfAlwaysDst1LineEdit false true You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. :/icons/images/kontact_contacts.png Address book Select an address from the address book. :/icons/images/kontact_contacts.png Address book Select an address from the address book. :/icons/images/kontact_contacts.png Address book Select an address from the address book. &No answer &Redirect calls when I do not answer Alt+R Activate the redirection on no answer service. true Redirect to true You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. &3rd choice destination: cfAlwaysDst3LineEdit false &2nd choice destination: cfAlwaysDst2LineEdit false &1st choice destination: cfAlwaysDst1LineEdit false true You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. true You can specify up to 3 destinations to which you want to redirect the call. If the first destination does not answer the call, the second destination will be tried and so on. :/icons/images/kontact_contacts.png Address book Select an address from the address book. :/icons/images/kontact_contacts.png Address book Select an address from the address book. :/icons/images/kontact_contacts.png Address book Select an address from the address book. 20 16 QSizePolicy::Expanding Qt::Vertical 261 20 QSizePolicy::Expanding Qt::Horizontal &OK Alt+O true Accept and save all changes. &Cancel Alt+C Undo your changes and close the window. cfTabWidget cfAlwaysCheckBox cfAlwaysDst1LineEdit cfAlwaysDst2LineEdit cfAlwaysDst3LineEdit cfBusyCheckBox cfBusyDst1LineEdit cfBusyDst2LineEdit cfBusyDst3LineEdit cfNoanswerCheckBox cfNoanswerDst1LineEdit cfNoanswerDst2LineEdit cfNoanswerDst3LineEdit okPushButton cancelPushButton sockets/url.h list qlineedit.h ui_getaddressform.h user.h phone.h cancelPushButton clicked() SrvRedirectForm reject() okPushButton clicked() SrvRedirectForm validate() cfAlwaysCheckBox toggled(bool) SrvRedirectForm toggleAlways(bool) cfBusyCheckBox toggled(bool) SrvRedirectForm toggleBusy(bool) cfNoanswerCheckBox toggled(bool) SrvRedirectForm toggleNoanswer(bool) addrAlways1ToolButton clicked() SrvRedirectForm showAddressBook1() addrAlways2ToolButton clicked() SrvRedirectForm showAddressBook2() addrAlways3ToolButton clicked() SrvRedirectForm showAddressBook3() addrBusy1ToolButton clicked() SrvRedirectForm showAddressBook4() addrBusy2ToolButton clicked() SrvRedirectForm showAddressBook5() addrBusy3ToolButton clicked() SrvRedirectForm showAddressBook6() addrNoanswer1ToolButton clicked() SrvRedirectForm showAddressBook7() addrNoanswer2ToolButton clicked() SrvRedirectForm showAddressBook8() addrNoanswer3ToolButton clicked() SrvRedirectForm showAddressBook9() userComboBox activated(QString) SrvRedirectForm changedUser(QString) twinkle-1.10.1/src/gui/syssettingsform.cpp000066400000000000000000000431461277565361200206370ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include "gui.h" #include "sockets/interfaces.h" #include "selectprofileform.h" #include #include "audits/memman.h" #include #include #include #include "twinkle_config.h" #include #include #include "syssettingsform.h" /* * Constructs a SysSettingsForm as a child of 'parent', with the * name 'name' and widget flags set to 'f'. * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ SysSettingsForm::SysSettingsForm(QWidget* parent) : QDialog(parent) { setupUi(this); init(); } /* * Destroys the object and frees any allocated resources */ SysSettingsForm::~SysSettingsForm() { // no need to delete child widgets, Qt does it all for us } /* * Sets the strings of the subwidgets using the current * language. */ void SysSettingsForm::languageChange() { retranslateUi(this); } // Indices of categories in the category list box #define idxCatGeneral 0 #define idxCatAudio 1 #define idxCatRingtones 2 #define idxCatAddressBook 3 #define idxCatNetwork 4 #define idxCatLog 5 void SysSettingsForm::init() { // Set toolbutton icons for disabled options. QIcon i; i = openRingtoneToolButton->icon(); i.addPixmap(QPixmap(":/icons/images/fileopen-disabled.png"), QIcon::Disabled); openRingtoneToolButton->setIcon(i); i = openRingbackToolButton->icon(); i.addPixmap(QPixmap(":/icons/images/fileopen-disabled.png"), QIcon::Disabled); openRingbackToolButton->setIcon(i); QRegExp rxNumber("[0-9]+"); maxUdpSizeLineEdit->setValidator(new QRegExpValidator(rxNumber, this)); maxTcpSizeLineEdit->setValidator(new QRegExpValidator(rxNumber, this)); } void SysSettingsForm::showCategory( int index ) { if (index == idxCatGeneral) { settingsWidgetStack->setCurrentWidget(pageGeneral); } else if (index == idxCatAudio) { settingsWidgetStack->setCurrentWidget(pageAudio); } else if (index == idxCatRingtones) { settingsWidgetStack->setCurrentWidget(pageRingtones); } else if (index == idxCatAddressBook) { settingsWidgetStack->setCurrentWidget(pageAddressBook); } else if (index == idxCatNetwork) { settingsWidgetStack->setCurrentWidget(pageNetwork); } else if (index == idxCatLog) { settingsWidgetStack->setCurrentWidget(pageLog); } } string SysSettingsForm::comboItem2audio_dev(QString item, QLineEdit *qleOther, bool playback) { if (item == QString("ALSA: ") + DEV_OTHER) { if (qleOther->text().isEmpty()) return ""; return (QString(PFX_ALSA) + qleOther->text()).toStdString(); } if (item == QString("OSS: ") + DEV_OTHER) { if (qleOther->text().isEmpty()) return ""; return (QString(PFX_OSS) + qleOther->text()).toStdString(); } list &list_audio_dev = (playback ? list_audio_playback_dev : list_audio_capture_dev); for (list::iterator i = list_audio_dev.begin(); i != list_audio_dev.end(); i++) { if (i->get_description() == item.toStdString()) { return i->get_settings_value(); } } return ""; } void SysSettingsForm::populateComboBox(QComboBox *cb, const QString &s) { for (int i = 0; i < cb->count(); i++) { if (cb->itemText(i) == s) { cb->setCurrentIndex(i); return; } } } void SysSettingsForm::populate() { QString msg; int idx; // Select the Audio category categoryListBox->setCurrentRow(idxCatGeneral); settingsWidgetStack->setCurrentWidget(pageGeneral); // Set focus on first field categoryListBox->setFocus(); // Audio settings list_audio_playback_dev = sys_config->get_audio_devices(true); list_audio_capture_dev = sys_config->get_audio_devices(false); ringtoneComboBox->clear(); speakerComboBox->clear(); micComboBox->clear(); bool devRingtoneFound = false; bool devSpeakerFound = false; bool devMicFound = false; // Playback devices idx = 0; for (list::iterator i = list_audio_playback_dev.begin(); i != list_audio_playback_dev.end(); i++, idx++) { string item = i->get_description(); ringtoneComboBox->addItem(QString(item.c_str())); speakerComboBox->addItem(QString(item.c_str())); // Select audio device if (sys_config->get_dev_ringtone().device == i->device) { ringtoneComboBox->setCurrentIndex(idx); otherRingtoneLineEdit->clear(); devRingtoneFound = true; } if (sys_config->get_dev_speaker().device == i->device) { speakerComboBox->setCurrentIndex(idx); otherSpeakerLineEdit->clear(); devSpeakerFound = true; } // Determine index for other non-standard device if (i->device == DEV_OTHER) { if (i->type == t_audio_device::ALSA) { idxOtherPlaybackDevAlsa = idx; } else { idxOtherPlaybackDevOss = idx; } } } // Check for non-standard audio devices if (!devRingtoneFound) { t_audio_device dev = sys_config->get_dev_ringtone(); otherRingtoneLineEdit->setText(dev.device.c_str()); ringtoneComboBox->setCurrentIndex( (dev.type == t_audio_device::ALSA ? idxOtherPlaybackDevAlsa : idxOtherPlaybackDevOss)); } if (!devSpeakerFound) { t_audio_device dev = sys_config->get_dev_speaker(); otherSpeakerLineEdit->setText(dev.device.c_str()); speakerComboBox->setCurrentIndex( (dev.type == t_audio_device::ALSA ? idxOtherPlaybackDevAlsa : idxOtherPlaybackDevOss)); } // Capture device idx = 0; for (list::iterator i = list_audio_capture_dev.begin(); i != list_audio_capture_dev.end(); i++, idx++) { string item = i->get_description(); micComboBox->addItem(QString(item.c_str())); // Select audio device if (sys_config->get_dev_mic().device == i->device) { micComboBox->setCurrentIndex(idx); otherMicLineEdit->clear(); devMicFound = true; } // Determine index for other non-standard device if (i->device == DEV_OTHER) { if (i->type == t_audio_device::ALSA) { idxOtherCaptureDevAlsa = idx; } else { idxOtherCaptureDevOss = idx; } } } // Check for non-standard audio devices if (!devMicFound) { t_audio_device dev = sys_config->get_dev_mic(); otherMicLineEdit->setText(dev.device.c_str()); micComboBox->setCurrentIndex( (dev.type == t_audio_device::ALSA ? idxOtherCaptureDevAlsa : idxOtherCaptureDevOss)); } // Enable/disable line edit for non-standard device devRingtoneSelected(ringtoneComboBox->currentIndex()); devSpeakerSelected(speakerComboBox->currentIndex()); devMicSelected(micComboBox->currentIndex()); validateAudioCheckBox->setChecked(sys_config->get_validate_audio_dev()); populateComboBox(ossFragmentComboBox, QString::number(sys_config->get_oss_fragment_size())); populateComboBox(alsaPlayPeriodComboBox, QString::number(sys_config->get_alsa_play_period_size())); populateComboBox(alsaCapturePeriodComboBox, QString::number(sys_config->get_alsa_capture_period_size())); // Log settings logMaxSizeSpinBox->setValue(sys_config->get_log_max_size()); logDebugCheckBox->setChecked(sys_config->get_log_show_debug()); logSipCheckBox->setChecked(sys_config->get_log_show_sip()); logStunCheckBox->setChecked(sys_config->get_log_show_stun()); logMemoryCheckBox->setChecked(sys_config->get_log_show_memory()); // General settings guiUseSystrayCheckBox->setChecked(sys_config->get_gui_use_systray()); guiHideCheckBox->setChecked(sys_config->get_gui_hide_on_close()); guiHideCheckBox->setEnabled(sys_config->get_gui_use_systray()); // Call history histSizeSpinBox->setValue(sys_config->get_ch_max_size()); // Auto show on incoming call autoShowCheckBox->setChecked(sys_config->get_gui_auto_show_incoming()); autoShowTimeoutSpinBox->setValue(sys_config->get_gui_auto_show_timeout()); // Services callWaitingCheckBox->setChecked(sys_config->get_call_waiting()); hangupBothCheckBox->setChecked(sys_config->get_hangup_both_3way()); // Startup settings startHiddenCheckBox->setChecked(sys_config->get_start_hidden()); osdCheckBox->setChecked(sys_config->get_gui_show_call_osd()); QStringList profiles; if (!SelectProfileForm::getUserProfiles(profiles, msg)) { ((t_gui *)ui)->cb_show_msg(this, msg.toStdString(), MSG_CRITICAL); } profileListView->clear(); for (QStringList::Iterator i = profiles.begin(); i != profiles.end(); i++) { // Strip off the .cfg suffix QString profile = *i; profile.truncate(profile.length() - 4); QListWidgetItem* item = new QListWidgetItem(profile, profileListView); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(Qt::Unchecked); item->setData(Qt::DecorationRole, QPixmap(":/icons/images/penguin-small.png")); list l = sys_config->get_start_user_profiles(); if (std::find(l.begin(), l.end(), profile.toStdString()) != l.end()) { item->setCheckState(Qt::Checked); } } // Web browser command browserLineEdit->setText(sys_config->get_gui_browser_cmd().c_str()); // Network settings sipUdpPortSpinBox->setValue(sys_config->get_config_sip_port()); rtpPortSpinBox->setValue(sys_config->get_rtp_port()); maxUdpSizeLineEdit->setText(QString::number(sys_config->get_sip_max_udp_size())); maxTcpSizeLineEdit->setText(QString::number(sys_config->get_sip_max_tcp_size())); // Ring tone settings playRingtoneCheckBox->setChecked(sys_config->get_play_ringtone()); defaultRingtoneRadioButton->setChecked(sys_config->get_ringtone_file().empty()); customRingtoneRadioButton->setChecked(!sys_config->get_ringtone_file().empty()); ringtoneLineEdit->setText(sys_config->get_ringtone_file().c_str()); defaultRingtoneRadioButton->setEnabled(sys_config->get_play_ringtone()); customRingtoneRadioButton->setEnabled(sys_config->get_play_ringtone()); ringtoneLineEdit->setEnabled(!sys_config->get_ringtone_file().empty()); openRingtoneToolButton->setEnabled(!sys_config->get_ringtone_file().empty()); playRingbackCheckBox->setChecked(sys_config->get_play_ringback()); defaultRingbackRadioButton->setChecked(sys_config->get_ringback_file().empty()); customRingbackRadioButton->setChecked(!sys_config->get_ringback_file().empty()); ringbackLineEdit->setText(sys_config->get_ringback_file().c_str()); defaultRingbackRadioButton->setEnabled(sys_config->get_play_ringback()); customRingbackRadioButton->setEnabled(sys_config->get_play_ringback()); ringbackLineEdit->setEnabled(!sys_config->get_ringback_file().empty()); openRingbackToolButton->setEnabled(!sys_config->get_ringback_file().empty()); // Address book settings abLookupNameCheckBox->setChecked(sys_config->get_ab_lookup_name()); abOverrideDisplayCheckBox->setChecked(sys_config->get_ab_override_display()); abOverrideDisplayCheckBox->setEnabled(sys_config->get_ab_lookup_name()); abLookupPhotoCheckBox->setChecked(sys_config->get_ab_lookup_photo()); } void SysSettingsForm::validate() { bool conversion_ok = false; unsigned short sip_max_udp_size = maxUdpSizeLineEdit->text().toUShort(&conversion_ok); if (!conversion_ok) sip_max_udp_size = sys_config->get_sip_max_udp_size(); unsigned long sip_max_tcp_size = maxTcpSizeLineEdit->text().toULong(&conversion_ok); if (!conversion_ok) sip_max_tcp_size = sys_config->get_sip_max_tcp_size(); // Audio string dev; dev = comboItem2audio_dev(ringtoneComboBox->currentText(), otherRingtoneLineEdit, true); if (dev != "") sys_config->set_dev_ringtone(sys_config->audio_device(dev)); dev = comboItem2audio_dev(speakerComboBox->currentText(), otherSpeakerLineEdit, true); if (dev != "") sys_config->set_dev_speaker(sys_config->audio_device(dev)); dev = comboItem2audio_dev(micComboBox->currentText(), otherMicLineEdit, false); if (dev != "") sys_config->set_dev_mic(sys_config->audio_device(dev)); sys_config->set_validate_audio_dev(validateAudioCheckBox->isChecked()); sys_config->set_oss_fragment_size( ossFragmentComboBox->currentText().toInt()); sys_config->set_alsa_play_period_size( alsaPlayPeriodComboBox->currentText().toInt()); sys_config->set_alsa_capture_period_size( alsaCapturePeriodComboBox->currentText().toInt()); // Log sys_config->set_log_max_size(logMaxSizeSpinBox->value()); sys_config->set_log_show_debug(logDebugCheckBox->isChecked()); sys_config->set_log_show_sip(logSipCheckBox->isChecked()); sys_config->set_log_show_stun(logStunCheckBox->isChecked()); sys_config->set_log_show_memory(logMemoryCheckBox->isChecked()); // General sys_config->set_gui_use_systray(guiUseSystrayCheckBox->isChecked()); sys_config->set_gui_hide_on_close(guiHideCheckBox->isChecked()); sys_config->set_gui_show_call_osd(osdCheckBox->isChecked()); // Auto show on incoming call sys_config->set_gui_auto_show_incoming(autoShowCheckBox->isChecked()); sys_config->set_gui_auto_show_timeout(autoShowTimeoutSpinBox->value()); // Call history sys_config->set_ch_max_size(histSizeSpinBox->value()); // Services sys_config->set_call_waiting(callWaitingCheckBox->isChecked()); sys_config->set_hangup_both_3way(hangupBothCheckBox->isChecked()); // Startup sys_config->set_start_hidden(startHiddenCheckBox->isChecked() && guiUseSystrayCheckBox->isChecked()); list start_user_profiles; for (int i = 0; i < profileListView->count(); i++) { QListWidgetItem *item = profileListView->item(i); if (item->checkState() == Qt::Checked) start_user_profiles.push_back(item->text().toStdString()); } sys_config->set_start_user_profiles(start_user_profiles); // Web browser command sys_config->set_gui_browser_cmd(browserLineEdit->text().trimmed().toStdString()); // Network if (sys_config->get_config_sip_port() != sipUdpPortSpinBox->value()) { sys_config->set_config_sip_port(sipUdpPortSpinBox->value()); emit sipUdpPortChanged(); } if (sys_config->get_rtp_port() != rtpPortSpinBox->value()) { sys_config->set_rtp_port(rtpPortSpinBox->value()); emit rtpPortChanged(); } sys_config->set_sip_max_udp_size(sip_max_udp_size); sys_config->set_sip_max_tcp_size(sip_max_tcp_size); // Ring tones sys_config->set_play_ringtone(playRingtoneCheckBox->isChecked()); if (sys_config->get_play_ringtone()) { if (defaultRingtoneRadioButton->isChecked()) { sys_config->set_ringtone_file(""); } else { sys_config->set_ringtone_file(ringtoneLineEdit-> text().trimmed().toStdString()); } } else { sys_config->set_ringtone_file(""); } sys_config->set_play_ringback(playRingbackCheckBox->isChecked()); if (sys_config->get_play_ringback()) { if (defaultRingbackRadioButton->isChecked()) { sys_config->set_ringback_file(""); } else { sys_config->set_ringback_file(ringbackLineEdit-> text().trimmed().toStdString()); } } else { sys_config->set_ringback_file(""); } // Address book settings sys_config->set_ab_lookup_name(abLookupNameCheckBox->isChecked()); sys_config->set_ab_override_display(abOverrideDisplayCheckBox->isChecked()); sys_config->set_ab_lookup_photo(abLookupPhotoCheckBox->isChecked()); // Save user config string error_msg; if (!sys_config->write_config(error_msg)) { // Failed to write config file ((t_gui *)ui)->cb_show_msg(this, error_msg, MSG_CRITICAL); return; } accept(); } void SysSettingsForm::show() { populate(); QDialog::show(); } int SysSettingsForm::exec() { populate(); return QDialog::exec(); } void SysSettingsForm::chooseRingtone() { QString file = QFileDialog::getOpenFileName(this, tr("Choose ring tone"), ((t_gui *)ui)->get_last_file_browse_path(), tr("Ring tones", "Description of .wav files in file dialog").append(" (*.wav)")); if (!file.isEmpty()) { ringtoneLineEdit->setText(file); ((t_gui *)ui)->set_last_file_browse_path(QFileInfo(file).absolutePath()); } } void SysSettingsForm::chooseRingback() { QString file = QFileDialog::getOpenFileName(this, tr("Choose ring back tone"), ((t_gui *)ui)->get_last_file_browse_path(), tr("Ring back tones", "Description of .wav files in file dialog").append(" (*.wav)")); if (!file.isEmpty()) { ringbackLineEdit->setText(file); ((t_gui *)ui)->set_last_file_browse_path(QFileInfo(file).absolutePath()); } } void SysSettingsForm::devRingtoneSelected(int idx) { bool b = (idx == idxOtherPlaybackDevAlsa || idx == idxOtherPlaybackDevOss); otherRingtoneTextLabel->setEnabled(b); otherRingtoneLineEdit->setEnabled(b); } void SysSettingsForm::devSpeakerSelected(int idx) { bool b = (idx == idxOtherPlaybackDevAlsa || idx == idxOtherPlaybackDevOss); otherSpeakerTextLabel->setEnabled(b); otherSpeakerLineEdit->setEnabled(b); } void SysSettingsForm::devMicSelected(int idx) { bool b = (idx == idxOtherCaptureDevAlsa || idx == idxOtherCaptureDevOss); otherMicTextLabel->setEnabled(b); otherMicLineEdit->setEnabled(b); } void SysSettingsForm::playRingToneCheckBoxToggles(bool on) { if (on) { ringtoneLineEdit->setEnabled(customRingtoneRadioButton->isChecked()); } else { ringtoneLineEdit->setEnabled(false); } } void SysSettingsForm::playRingBackToneCheckBoxToggles(bool on) { if (on) { ringbackLineEdit->setEnabled(customRingbackRadioButton->isChecked()); } else { ringbackLineEdit->setEnabled(false); } } twinkle-1.10.1/src/gui/syssettingsform.h000066400000000000000000000023531277565361200202770ustar00rootroot00000000000000#ifndef SYSSETTINGSFORM_H #define SYSSETTINGSFORM_H #include "sys_settings.h" #include #include "ui_syssettingsform.h" class SysSettingsForm : public QDialog, public Ui::SysSettingsForm { Q_OBJECT public: SysSettingsForm(QWidget* parent = 0); ~SysSettingsForm(); virtual string comboItem2audio_dev( QString item, QLineEdit * qleOther, bool playback ); public slots: virtual void showCategory( int index ); virtual void populateComboBox( QComboBox * cb, const QString & s ); virtual void populate(); virtual void validate(); virtual void show(); virtual int exec(); virtual void chooseRingtone(); virtual void chooseRingback(); virtual void devRingtoneSelected( int idx ); virtual void devSpeakerSelected( int idx ); virtual void devMicSelected( int idx ); virtual void playRingToneCheckBoxToggles( bool on ); virtual void playRingBackToneCheckBoxToggles( bool on ); signals: void sipUdpPortChanged(); void rtpPortChanged(); protected slots: virtual void languageChange(); private: int idxOtherCaptureDevOss; int idxOtherCaptureDevAlsa; int idxOtherPlaybackDevOss; int idxOtherPlaybackDevAlsa; list list_audio_playback_dev; list list_audio_capture_dev; void init(); }; #endif twinkle-1.10.1/src/gui/syssettingsform.ui000066400000000000000000001662541277565361200205000ustar00rootroot00000000000000 SysSettingsForm 0 0 763 616 Twinkle - System Settings 0 0 Select a category for which you want to see or modify the settings. 32 32 0 General :/icons/images/twinkle32.png:/icons/images/twinkle32.png Audio :/icons/images/kmix.png:/icons/images/kmix.png Ring tones :/icons/images/knotify.png:/icons/images/knotify.png Address book :/icons/images/kontact_contacts32.png:/icons/images/kontact_contacts32.png Network :/icons/images/network.png:/icons/images/network.png Log :/icons/images/log.png:/icons/images/log.png Qt::Horizontal QSizePolicy::Expanding 321 20 Accept and save your changes. &OK Alt+O true Undo all your changes and close the window. &Cancel Alt+C 0 0 QFrame::StyledPanel 2 Sound Card 0 0 Select the sound card for playing the ring tone for incoming calls. 0 0 Select the sound card to which your microphone is connected. 0 0 Select the sound card for the speaker function during a call. &Speaker: false speakerComboBox &Ring tone: false ringtoneComboBox Other device: false otherRingtoneLineEdit Other device: false otherSpeakerLineEdit Other device: false otherMicLineEdit &Microphone: false micComboBox <p> Twinkle validates the audio devices before usage to avoid an established call without an audio channel. <p> On startup of Twinkle a warning is given if an audio device is inaccessible. <p> If before making a call, the microphone or speaker appears to be invalid, a warning is given and no call can be made. <p> If before answering a call, the microphone or speaker appears to be invalid, a warning is given and the call will not be answered. &Validate devices before usage Alt+V Advanced OSS &fragment size: false ossFragmentComboBox The ALSA play period size influences the real time behaviour of your soundcard for playing sound. If your sound frequently drops while using ALSA, you might try a different value here. 16 32 64 128 256 512 1024 ALSA &play period size: false alsaPlayPeriodComboBox &ALSA capture period size: false alsaCapturePeriodComboBox The OSS fragment size influences the real time behaviour of your soundcard. If your sound frequently drops while using OSS, you might try a different value here. 16 32 64 128 256 512 1024 The ALSA capture period size influences the real time behaviour of your soundcard for capturing sound. If the other side of your call complains about frequently dropping sound, you might try a different value here. 16 32 64 128 256 512 1024 Qt::Horizontal QSizePolicy::Expanding 121 20 Tip: for crackling sound with PulseAudio, set play period size to maximum. true Qt::Vertical QSizePolicy::Expanding 20 20 21 QFrame::StyledPanel Audio false 10 21 QFrame::StyledPanel Log false 10 &Max log size: false logMaxSizeSpinBox The maximum size of a log file in MB. When the log file exceeds this size, a backup of the log file is created and the current log file is zapped. Only one backup log file will be kept. 1 100 5 5 MB false Qt::Horizontal QSizePolicy::Expanding 211 20 Indicates if reports marked as "debug" will be logged. Log &debug reports Alt+D Indicates if SIP messages will be logged. Log &SIP reports Alt+S Indicates if STUN messages will be logged. Log S&TUN reports Alt+T Indicates if reports concerning memory management will be logged. Log m&emory reports Alt+E Qt::Vertical QSizePolicy::Expanding 20 61 21 QFrame::StyledPanel General false 10 System tray Enable this option if you want a system tray icon for Twinkle. The system tray icon is created when you start Twinkle. Create &system tray icon on startup Alt+S Enable this option if you want Twinkle to hide in the system tray when you close the main window. &Hide in system tray when closing main window Alt+H Startup Next time you start Twinkle it will immediately hide in the system tray. This works best when you also select a default user profile. S&tartup hidden in system tray Alt+T If you always use the same profile(s), then you can mark these profiles as default here. The next time you start Twinkle, you will not be asked to select which profiles to run. The default profiles will automatically run. QAbstractItemView::NoSelection QListView::Fixed Services With call waiting an incoming call is accepted when only one line is busy. When you disable call waiting an incoming call will be rejected when one line is busy. Call &waiting Alt+W Hang up both lines when you press bye to end a 3-way conference call. When this option is disabled, only the active line will be hung up and you can continue talking with the party on the other line. Hang up &both lines when ending a 3-way conference call. Alt+B Enable in-call OSD &Maximum calls in call history: false histSizeSpinBox The maximum number of calls that will be kept in the call history. 1000 10 Qt::Horizontal QSizePolicy::Expanding 191 20 When the main window is hidden, it will be automatically shown on an incoming call after the number of specified seconds. &Auto show main window on incoming call after Alt+A Number of seconds after which the main window should be shown. 60 secs false Qt::Horizontal QSizePolicy::Expanding 29 20 W&eb browser command: false browserLineEdit Command to start your web browser. If you leave this field empty Twinkle will try to figure out your default web browser. 21 QFrame::StyledPanel Network false 10 Qt::Vertical QSizePolicy::Expanding 20 230 Qt::Horizontal QSizePolicy::Expanding 314 20 Maximum allowed size (0-65535) in bytes of an incoming SIP message over UDP. &SIP port: false sipUdpPortSpinBox &RTP port: false rtpPortSpinBox Max. SIP message size (&TCP): false maxTcpSizeLineEdit Qt::Horizontal QSizePolicy::Expanding 314 20 The UDP/TCP port used for sending and receiving SIP messages. 1025 65535 5060 Max. SIP message size (&UDP): false maxUdpSizeLineEdit Maximum allowed size (0-4294967295) in bytes of an incoming SIP message over TCP. The UDP port used for sending and receiving RTP for the first line. The UDP port for the second line is 2 higher. E.g. if port 8000 is used for the first line, then the second line uses port 8002. When you use call transfer then the next even port (eg. 8004) is also used. 1025 65535 2 8000 21 QFrame::StyledPanel Ring tones false 10 Ring tone Indicates if a ring tone should be played when a call comes in. &Play ring tone on incoming call Alt+P Play the default ring tone when a call comes in. &Default ring tone Alt+D true Play a custom ring tone when a call comes in. C&ustom ring tone Alt+U Qt::Horizontal QSizePolicy::Fixed 20 20 Specify the file name of a .wav file that you want to be played as ring tone. Qt::TabFocus Select ring tone file. :/icons/images/fileopen.png:/icons/images/fileopen.png Ring back tone <p> Play ring back tone while you are waiting for the far-end to answer your call. </p> <p> Depending on your SIP provider the network might provide ring back tone or an announcement. </p> P&lay ring back tone when network does not play ring back tone Alt+L Play the default ring back tone. D&efault ring back tone Alt+E true Play a custom ring back tone. Cu&stom ring back tone Alt+S Qt::Horizontal QSizePolicy::Fixed 20 20 Specify the file name of a .wav file that you want to be played as ring back tone. Qt::TabFocus Select ring back tone file. :/icons/images/fileopen.png:/icons/images/fileopen.png Qt::Vertical QSizePolicy::Expanding 20 20 21 QFrame::StyledPanel Address book false 10 On an incoming call, Twinkle will try to find the name belonging to the incoming SIP address in your address book. This name will be displayed. &Lookup name for incoming call Alt+L The caller may have provided a display name already. Tick this box if you want to override that name with the name you have in your address book. Ove&rride received display name Alt+R Lookup the photo of a caller in your address book and display it on an incoming call. Lookup &photo for incoming call Alt+P Qt::Vertical QSizePolicy::Expanding 20 121 categoryListBox guiUseSystrayCheckBox guiHideCheckBox startHiddenCheckBox profileListView callWaitingCheckBox hangupBothCheckBox histSizeSpinBox autoShowCheckBox autoShowTimeoutSpinBox ringtoneComboBox otherRingtoneLineEdit speakerComboBox otherSpeakerLineEdit micComboBox otherMicLineEdit validateAudioCheckBox ossFragmentComboBox alsaPlayPeriodComboBox alsaCapturePeriodComboBox playRingtoneCheckBox defaultRingtoneRadioButton ringtoneLineEdit openRingtoneToolButton playRingbackCheckBox defaultRingbackRadioButton ringbackLineEdit openRingbackToolButton abLookupNameCheckBox abOverrideDisplayCheckBox abLookupPhotoCheckBox sipUdpPortSpinBox rtpPortSpinBox maxUdpSizeLineEdit maxTcpSizeLineEdit logMaxSizeSpinBox logDebugCheckBox logSipCheckBox logStunCheckBox logMemoryCheckBox okPushButton cancelPushButton customRingtoneRadioButton customRingbackRadioButton sys_settings.h okPushButton clicked() SysSettingsForm validate() 20 20 20 20 cancelPushButton clicked() SysSettingsForm reject() 20 20 20 20 categoryListBox currentRowChanged(int) SysSettingsForm showCategory(int) 20 20 20 20 guiUseSystrayCheckBox toggled(bool) guiHideCheckBox setEnabled(bool) 20 20 20 20 guiUseSystrayCheckBox toggled(bool) guiHideCheckBox setChecked(bool) 20 20 20 20 guiUseSystrayCheckBox toggled(bool) startHiddenCheckBox setEnabled(bool) 20 20 20 20 playRingtoneCheckBox toggled(bool) customRingtoneRadioButton setEnabled(bool) 20 20 20 20 playRingtoneCheckBox toggled(bool) defaultRingtoneRadioButton setEnabled(bool) 20 20 20 20 playRingtoneCheckBox toggled(bool) SysSettingsForm playRingToneCheckBoxToggles(bool) 20 20 20 20 playRingtoneCheckBox toggled(bool) openRingtoneToolButton setEnabled(bool) 20 20 20 20 playRingbackCheckBox toggled(bool) customRingbackRadioButton setEnabled(bool) 20 20 20 20 playRingbackCheckBox toggled(bool) defaultRingbackRadioButton setEnabled(bool) 20 20 20 20 playRingbackCheckBox toggled(bool) SysSettingsForm playRingBackToneCheckBoxToggles(bool) 20 20 20 20 playRingbackCheckBox toggled(bool) openRingbackToolButton setEnabled(bool) 20 20 20 20 openRingtoneToolButton clicked() SysSettingsForm chooseRingtone() 20 20 20 20 openRingbackToolButton clicked() SysSettingsForm chooseRingback() 20 20 20 20 customRingtoneRadioButton toggled(bool) ringtoneLineEdit setEnabled(bool) 20 20 20 20 customRingtoneRadioButton toggled(bool) openRingtoneToolButton setEnabled(bool) 20 20 20 20 customRingbackRadioButton toggled(bool) ringbackLineEdit setEnabled(bool) 20 20 20 20 customRingbackRadioButton toggled(bool) openRingbackToolButton setEnabled(bool) 20 20 20 20 abLookupNameCheckBox toggled(bool) abOverrideDisplayCheckBox setEnabled(bool) 20 20 20 20 ringtoneComboBox activated(int) SysSettingsForm devRingtoneSelected(int) 20 20 20 20 speakerComboBox activated(int) SysSettingsForm devSpeakerSelected(int) 20 20 20 20 micComboBox activated(int) SysSettingsForm devMicSelected(int) 20 20 20 20 twinkle-1.10.1/src/gui/termcapform.cpp000066400000000000000000000060631277565361200176700ustar00rootroot00000000000000#include "termcapform.h" /* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include "gui.h" #include "audits/memman.h" #include "termcapform.h" /* * Constructs a TermCapForm as a child of 'parent', with the * name 'name' and widget flags set to 'f'. * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ TermCapForm::TermCapForm(QWidget* parent) : QDialog(parent) { setupUi(this); init(); } /* * Destroys the object and frees any allocated resources */ TermCapForm::~TermCapForm() { destroy(); // no need to delete child widgets, Qt does it all for us } /* * Sets the strings of the subwidgets using the current * language. */ void TermCapForm::languageChange() { retranslateUi(this); } void TermCapForm::init() { getAddressForm = 0; // Set toolbutton icons for disabled options. setDisabledIcon(addressToolButton, ":/icons/images/kontact_contacts-disabled.png"); } void TermCapForm::show(t_user *user_config, const QString &dest) { ((t_gui *)ui)->fill_user_combo(fromComboBox); // Select from user if (user_config) { for (int i = 0; i < fromComboBox->count(); i++) { if (fromComboBox->itemText(i) == user_config->get_profile_name().c_str()) { fromComboBox->setCurrentIndex(i); break; } } } partyLineEdit->setText(dest); QDialog::show(); } void TermCapForm::destroy() { if (getAddressForm) { MEMMAN_DELETE(getAddressForm); delete getAddressForm; } } void TermCapForm::validate() { string display, dest_str; t_user *from_user = phone->ref_user_profile( fromComboBox->currentText().toStdString()); ui->expand_destination(from_user, partyLineEdit->text().trimmed().toStdString(), display, dest_str); t_url dest(dest_str); if (dest.is_valid()) { emit destination(from_user, dest); accept(); } else { partyLineEdit->selectAll(); } } void TermCapForm::showAddressBook() { if (!getAddressForm) { getAddressForm = new GetAddressForm(this); getAddressForm->setModal(true); MEMMAN_NEW(getAddressForm); } connect(getAddressForm, SIGNAL(address(const QString &)), this, SLOT(selectedAddress(const QString &))); getAddressForm->show(); } void TermCapForm::selectedAddress(const QString &address) { partyLineEdit->setText(address); } twinkle-1.10.1/src/gui/termcapform.h000066400000000000000000000013261277565361200173320ustar00rootroot00000000000000#ifndef TERMCAPFORM_H #define TERMCAPFORM_H #include "ui_termcapform.h" #include "getaddressform.h" #include "phone.h" #include "sockets/url.h" #include "user.h" class t_phone; extern t_phone *phone; class TermCapForm : public QDialog, public Ui::TermCapForm { Q_OBJECT public: TermCapForm(QWidget* parent = 0); ~TermCapForm(); public slots: virtual void show( t_user * user_config, const QString & dest ); virtual void validate(); virtual void showAddressBook(); virtual void selectedAddress( const QString & address ); signals: void destination(t_user *, const t_url &); protected slots: virtual void languageChange(); private: GetAddressForm *getAddressForm; void init(); void destroy(); }; #endif twinkle-1.10.1/src/gui/termcapform.ui000066400000000000000000000147521277565361200175270ustar00rootroot00000000000000 TermCapForm 0 0 581 168 5 5 0 0 Twinkle - Terminal Capabilities &From: fromComboBox false 7 0 0 0 Get terminal capabilities of &To: partyLineEdit false The address that you want to query for capabilities (OPTION request). This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Qt::TabFocus F10 :/icons/images/kontact_contacts.png Address book Select an address from the address book. 20 16 QSizePolicy::Expanding Qt::Vertical 131 20 QSizePolicy::Expanding Qt::Horizontal &OK true &Cancel partyLineEdit addressToolButton okPushButton cancelPushButton fromComboBox sockets/url.h ui_getaddressform.h user.h phone.h cancelPushButton clicked() TermCapForm reject() okPushButton clicked() TermCapForm validate() addressToolButton clicked() TermCapForm showAddressBook() twinkle-1.10.1/src/gui/textbrowsernoautolink.cpp000066400000000000000000000002751277565361200220440ustar00rootroot00000000000000#include "textbrowsernoautolink.h" TextBrowserNoAutoLink::TextBrowserNoAutoLink (QWidget* parent) : QTextBrowser(parent) { } void TextBrowserNoAutoLink::setSource(const QUrl & name) { } twinkle-1.10.1/src/gui/textbrowsernoautolink.h000066400000000000000000000033111277565361200215030ustar00rootroot00000000000000/**************************************************************************** ** ui.h extension file, included from the uic-generated form implementation. ** ** If you want to add, delete, or rename functions or slots, use ** Qt Designer to update this file, preserving your code. ** ** You should not define a constructor or destructor in this file. ** Instead, write your code in functions called init() and destroy(). ** These will automatically be called by the form's constructor and ** destructor. *****************************************************************************/ /* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _TEXTBROWSERNOAUTOLINK_H #define _TEXTBROWSERNOAUTOLINK_H #include /** * A text browser similar to QTextBrowser, but when a user clicks a link * the browser will not automatically load the linked document. */ class TextBrowserNoAutoLink : public QTextBrowser { Q_OBJECT public: TextBrowserNoAutoLink ( QWidget * parent = 0 ); virtual void setSource(const QUrl& name) override; }; #endif twinkle-1.10.1/src/gui/transferform.cpp000066400000000000000000000123371277565361200200620ustar00rootroot00000000000000//Added by qt3to4: #include #include #include #include #include #include "gui.h" #include "audits/memman.h" #include "transferform.h" /* * Constructs a TransferForm as a child of 'parent', with the * name 'name' and widget flags set to 'f'. * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ TransferForm::TransferForm(QWidget* parent) : QDialog(parent) { setupUi(this); init(); } /* * Destroys the object and frees any allocated resources */ TransferForm::~TransferForm() { destroy(); // no need to delete child widgets, Qt does it all for us } /* * Sets the strings of the subwidgets using the current * language. */ void TransferForm::languageChange() { retranslateUi(this); } /* Copyright (C) 2005-2009 Michel de Boer 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, see . */ void TransferForm::init() { getAddressForm = 0; // Set toolbutton icons for disabled options. QIcon i; i = addressToolButton->icon(); i.addPixmap(QPixmap(":/icons/images/kontact_contacts-disabled.png"), QIcon::Disabled); addressToolButton->setIcon(i); } void TransferForm::destroy() { if (getAddressForm) { MEMMAN_DELETE(getAddressForm); delete getAddressForm; } } void TransferForm::initTransferOptions() { // Show possible transfer type options // Basic transfer is always possible. // If a line is idle, then a transfer with consultation is possible. // The line will be seized, so an incoming call cannot occupy it. // If both lines are busy, then the active line can be transferred // to the other line. unsigned short idle_line; if (phone->get_idle_line(idle_line)) { consult_line = (int)idle_line; phone->pub_seize(consult_line); consultRadioButton->show(); consultRadioButton->setChecked(true); otherLineRadioButton->hide(); } else { consult_line = -1; consultRadioButton->hide(); otherLineRadioButton->show(); otherLineRadioButton->setChecked(true); } } void TransferForm::show(t_user *user) { user_config = user; initTransferOptions(); QDialog::show(); } void TransferForm::show(t_user *user, const string &dest, t_transfer_type transfer_type) { user_config = user; initTransferOptions(); toLineEdit->setText(dest.c_str()); switch (transfer_type) { case TRANSFER_CONSULT: consultRadioButton->setChecked(true); break; case TRANSFER_OTHER_LINE: otherLineRadioButton->setChecked(true); break; default: basicRadioButton->setChecked(true); break; } QDialog::show(); } void TransferForm::hide() { if (consult_line > -1) { phone->pub_unseize(consult_line); } QDialog::hide(); } void TransferForm::reject() { if (user_config->get_referrer_hold()) { ((t_gui *)ui)->action_retrieve(); } if (consult_line > -1) { phone->pub_unseize(consult_line); } QDialog::reject(); } void TransferForm::validate() { t_display_url dest; ui->expand_destination(user_config, toLineEdit->text().trimmed().toStdString(), dest); t_transfer_type transfer_type; if (consultRadioButton->isChecked()) { transfer_type = TRANSFER_CONSULT; } else if (otherLineRadioButton->isChecked()) { transfer_type = TRANSFER_OTHER_LINE; } else { transfer_type = TRANSFER_BASIC; } if (transfer_type == TRANSFER_OTHER_LINE || dest.is_valid()) { if (consult_line > -1) { phone->pub_unseize(consult_line); } emit destination(dest, transfer_type); accept(); } else { toLineEdit->selectAll(); } } void TransferForm::closeEvent(QCloseEvent *) { reject(); } void TransferForm::showAddressBook() { if (!getAddressForm) { getAddressForm = new GetAddressForm(this); getAddressForm->setModal(true); MEMMAN_NEW(getAddressForm); } connect(getAddressForm, SIGNAL(address(const QString &)), this, SLOT(selectedAddress(const QString &))); getAddressForm->show(); } void TransferForm::selectedAddress(const QString &address) { toLineEdit->setText(address); } void TransferForm::setOtherLineAddress(bool on) { if (on) { previousAddress = toLineEdit->text(); unsigned short active_line = phone->get_active_line(); unsigned short other_line = (active_line == 0 ? 1 : 0); QString address = ui->format_sip_address(user_config, phone->get_remote_display(other_line), phone->get_remote_uri(other_line)).c_str(); toLineEdit->setText(address); toLineEdit->setEnabled(false); toLabel->setEnabled(false); #ifdef HAVE_KDE addressToolButton->setEnabled(false); #endif } else { toLineEdit->setText(previousAddress); toLineEdit->setEnabled(true); toLabel->setEnabled(true); #ifdef HAVE_KDE addressToolButton->setEnabled(true); #endif } } twinkle-1.10.1/src/gui/transferform.h000066400000000000000000000021121277565361200175150ustar00rootroot00000000000000#ifndef TRANSFERFORM_H #define TRANSFERFORM_H class t_phone; extern t_phone *phone; #include "getaddressform.h" #include "phone.h" #include "protocol.h" #include #include "sockets/url.h" #include "user.h" #include "ui_transferform.h" class TransferForm : public QDialog, public Ui::TransferForm { Q_OBJECT public: TransferForm(QWidget* parent = 0); ~TransferForm(); public slots: virtual void initTransferOptions(); virtual void show( t_user * user ); virtual void show( t_user * user, const string & dest, t_transfer_type transfer_type ); virtual void hide(); virtual void reject(); virtual void validate(); virtual void closeEvent( QCloseEvent * ); virtual void showAddressBook(); virtual void selectedAddress( const QString & address ); virtual void setOtherLineAddress( bool on ); signals: void destination(const t_display_url&, t_transfer_type); protected slots: virtual void languageChange(); private: int consult_line; t_user *user_config; GetAddressForm *getAddressForm; QString previousAddress; void init(); void destroy(); }; #endif twinkle-1.10.1/src/gui/transferform.ui000066400000000000000000000146771277565361200177260ustar00rootroot00000000000000 TransferForm 0 0 532 251 Twinkle - Transfer Transfer call to &To: false toLineEdit The address of the person you want to transfer the call to. This can be a full SIP address like <b>sip:example@example.com</b> or just the user part or telephone number of the full address. When you do not specify a full address, then Twinkle will complete the address by using the domain value of your user profile. Qt::TabFocus Address book Select an address from the address book. :/icons/images/kontact_contacts.png:/icons/images/kontact_contacts.png F10 Transfer the call to a third party without contacting that third party yourself. &Blind transfer Alt+B true Before transferring the call to a third party, first consult the party yourself. T&ransfer with consultation Alt+R Connect the remote party on the active line with the remote party on the other line. Transfer to other &line Alt+L Qt::Vertical QSizePolicy::Expanding 20 20 Qt::Horizontal QSizePolicy::Expanding 121 20 &OK Alt+O true &Cancel qstring.h sockets/url.h ui_getaddressform.h user.h protocol.h phone.h okPushButton clicked() TransferForm validate() 20 20 20 20 cancelPushButton clicked() TransferForm reject() 20 20 20 20 addressToolButton clicked() TransferForm showAddressBook() 20 20 20 20 twinkle-1.10.1/src/gui/twinkleapplication.cpp000066400000000000000000000030441277565361200212460ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "twinkleapplication.h" #include "phone.h" #include "sys_settings.h" #include "user.h" #include "gui.h" extern t_phone *phone; #ifdef HAVE_KDE t_twinkle_application::t_twinkle_application() : KApplication() {} #else t_twinkle_application::t_twinkle_application(int &argc, char **argv) : QApplication(argc,argv) {} #endif void t_twinkle_application::commitData (QSessionManager &sm) { sys_config->set_ui_session_id(sessionId().toStdString()); // Create list of active profile file names list user_list = phone->ref_users(); list profile_filenames; for (list::const_iterator it = user_list.begin(); it != user_list.end(); ++it) { profile_filenames.push_back((*it)->get_filename()); } sys_config->set_ui_session_active_profiles(profile_filenames); ((t_gui*)ui)->save_session_state(); } twinkle-1.10.1/src/gui/twinkleapplication.h000066400000000000000000000022701277565361200207130ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _TWINKLEAPPLICATION_H #define _TWINKLEAPPLICATION_H #include "twinkle_config.h" #ifdef HAVE_KDE #include #else #include #endif #ifdef HAVE_KDE class t_twinkle_application : public KApplication { #else class t_twinkle_application : public QApplication { #endif public: #ifdef HAVE_KDE t_twinkle_application(); #else t_twinkle_application(int &argc, char **argv); #endif virtual void commitData ( QSessionManager &sm ); }; #endif twinkle-1.10.1/src/gui/userprofileform.cpp000066400000000000000000001532461277565361200206020ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include #include #include #include "sdp/sdp.h" #include #include "protocol.h" #include #include "gui.h" #include #include #include #include "twinkle_config.h" #include #include "numberconversionform.h" #include "util.h" #include "userprofileform.h" // Indices of categories in the category list box #define idxCatUser 0 #define idxCatSipServer 1 #define idxCatVoiceMail 2 #define idxCatIM 3 #define idxCatPresence 4 #define idxCatRtpAudio 5 #define idxCatSipProtocol 6 #define idxCatNat 7 #define idxCatAddrFmt 8 #define idxCatTimers 9 #define idxCatRingTones 10 #define idxCatScripts 11 #define idxCatSecurity 12 // Indices of call hold variants in the call hold variant list box #define idxHoldRfc2543 0 #define idxHoldRfc3264 1 // Indices of SIP extension support types in the list box #define idxExtDisabled 0 #define idxExtSupported 1 #define idxExtRequired 2 #define idxExtPreferred 3 // Indices of RTP audio tabs #define idxRtpCodecs 0 #define idxRtpPreprocessing 1 #define idxRtpIlbc 2 #define idxRtpSpeex 3 #define idxRtpDtmf 4 // Codec labels #define labelCodecG711a "G.711 A-law" #define labelCodecG711u "G.711 u-law" #define labelCodecGSM "GSM" #define labelCodecSpeexNb "speex-nb (8 kHz)" #define labelCodecSpeexWb "speex-wb (16 kHz)" #define labelCodecSpeexUwb "speex-uwb (32 kHz)" #define labelCodecIlbc "iLBC" #define labelCodecG726_16 "G.726 16 kbps" #define labelCodecG726_24 "G.726 24 kbps" #define labelCodecG726_32 "G.726 32 kbps" #define labelCodecG726_40 "G.726 40 kbps" #define labelCodecG729A "G.729A" // Indices of iLBC modes #define idxIlbcMode20 0 #define idxIlbcMode30 1 // Indices of G.726 packing modes #define idxG726PackRfc3551 0 #define idxG726PackAal2 1 // Indices of DTMF transport modes in the DTMF transport list box #define idxDtmfAuto 0 #define idxDtmfRfc2833 1 #define idxDtmfInband 2 #define idxDtmfInfo 3 // Columns in the number conversion list view #define colExpr 0 #define colReplace 1 // MWI type indices #define idxMWIUnsollicited 0 #define idxMWISollicited 1 // SIP transport protocol indices #define idxSipTransportAuto 0 #define idxSipTransportUDP 1 #define idxSipTransportTCP 2 /* * Constructs a UserProfileForm as a child of 'parent', with the * name 'name' and widget flags set to 'f'. * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ UserProfileForm::UserProfileForm(QWidget* parent) : QDialog(parent) { setupUi(this); init(); } /* * Destroys the object and frees any allocated resources */ UserProfileForm::~UserProfileForm() { // no need to delete child widgets, Qt does it all for us } /* * Sets the strings of the subwidgets using the current * language. */ void UserProfileForm::languageChange() { retranslateUi(this); } void UserProfileForm::init() { QRegExp rxNoSpace("\\S*"); QRegExp rxNoAtSign("[^@]*"); QRegExp rxQvalue("(0\\.[0-9]{0,3})|(1\\.0{0,3})"); QRegExp rxAkaOpValue("[a-zA-Z0-9]{0,32}"); QRegExp rxAkaAmfValue("[a-zA-Z0-9]{0,4}"); // Set validators // USER domainLineEdit->setValidator(new QRegExpValidator(rxNoSpace, this)); authAkaOpLineEdit->setValidator(new QRegExpValidator(rxAkaOpValue, this)); authAkaAmfLineEdit->setValidator(new QRegExpValidator(rxAkaAmfValue, this)); // SIP SERVER registrarLineEdit->setValidator(new QRegExpValidator(rxNoSpace, this)); regQvalueLineEdit->setValidator(new QRegExpValidator(rxQvalue, this)); proxyLineEdit->setValidator(new QRegExpValidator(rxNoSpace, this)); // Voice mail mwiServerLineEdit->setValidator(new QRegExpValidator(rxNoSpace, this)); // NAT publicIPLineEdit->setValidator(new QRegExpValidator(rxNoSpace, this)); // Address format testConversionLineEdit->setValidator(new QRegExpValidator(rxNoAtSign, this)); #ifndef HAVE_SPEEX // Speex & (Speex) Preprocessing speexGroupBox->hide(); preprocessingGroupBox->hide(); rtpAudioTabWidget->setTabEnabled(idxRtpSpeex, false); rtpAudioTabWidget->setTabEnabled(idxRtpPreprocessing, false); #endif #ifndef HAVE_ILBC // iLBC ilbcGroupBox->hide(); rtpAudioTabWidget->setTabEnabled(idxRtpIlbc, false); #endif #ifndef HAVE_ZRTP // Zrtp zrtpEnabledCheckBox->setEnabled(false); zrtpSettingsGroupBox->hide(); #endif // Set toolbutton icons for disabled options. QIcon i; i = openRingtoneToolButton->icon(); i.addPixmap(QPixmap(":/icons/images/fileopen-disabled.png"), QIcon::Disabled); openRingtoneToolButton->setIcon(i); openRingbackToolButton->setIcon(i); openIncomingCallScriptToolButton->setIcon(i); } void UserProfileForm::showCategory( int index ) { if (index == idxCatUser) { settingsWidgetStack->setCurrentWidget(pageUser); } else if (index == idxCatSipServer) { settingsWidgetStack->setCurrentWidget(pageSipServer); } else if (index == idxCatVoiceMail) { settingsWidgetStack->setCurrentWidget(pageVoiceMail); } else if (index == idxCatIM) { settingsWidgetStack->setCurrentWidget(pageIM); } else if (index == idxCatPresence) { settingsWidgetStack->setCurrentWidget(pagePresence); } else if (index == idxCatRtpAudio) { settingsWidgetStack->setCurrentWidget(pageRtpAudio); } else if (index == idxCatSipProtocol) { settingsWidgetStack->setCurrentWidget(pageSipProtocol); } else if (index == idxCatNat) { settingsWidgetStack->setCurrentWidget(pageNat); } else if (index == idxCatAddrFmt) { settingsWidgetStack->setCurrentWidget(pageAddressFormat); } else if (index == idxCatTimers) { settingsWidgetStack->setCurrentWidget(pageTimers); } else if (index == idxCatRingTones) { settingsWidgetStack->setCurrentWidget(pageRingTones); } else if (index == idxCatScripts) { settingsWidgetStack->setCurrentWidget(pageScripts); } else if (index == idxCatSecurity) { settingsWidgetStack->setCurrentWidget(pageSecurity); } } // Convert a label to a codec t_audio_codec UserProfileForm::label2codec(const QString &label) { if (label == labelCodecG711a) { return CODEC_G711_ALAW; } else if (label == labelCodecG711u) { return CODEC_G711_ULAW; } else if (label == labelCodecGSM) { return CODEC_GSM; } else if (label == labelCodecSpeexNb) { return CODEC_SPEEX_NB; } else if (label == labelCodecSpeexWb) { return CODEC_SPEEX_WB; } else if (label == labelCodecSpeexUwb) { return CODEC_SPEEX_UWB; } else if (label == labelCodecIlbc) { return CODEC_ILBC; } else if (label == labelCodecG726_16) { return CODEC_G726_16; } else if (label == labelCodecG726_24) { return CODEC_G726_24; } else if (label == labelCodecG726_32) { return CODEC_G726_32; } else if (label == labelCodecG726_40) { return CODEC_G726_40; } else if (label == labelCodecG729A) { return CODEC_G729A; } return CODEC_NULL; } // Convert a codec to a label QString UserProfileForm::codec2label(t_audio_codec &codec) { switch (codec) { case CODEC_G711_ALAW: return labelCodecG711a; case CODEC_G711_ULAW: return labelCodecG711u; case CODEC_GSM: return labelCodecGSM; case CODEC_SPEEX_NB: return labelCodecSpeexNb; case CODEC_SPEEX_WB: return labelCodecSpeexWb; case CODEC_SPEEX_UWB: return labelCodecSpeexUwb; case CODEC_ILBC: return labelCodecIlbc; case CODEC_G726_16: return labelCodecG726_16; case CODEC_G726_24: return labelCodecG726_24; case CODEC_G726_32: return labelCodecG726_32; case CODEC_G726_40: return labelCodecG726_40; case CODEC_G729A: return labelCodecG729A; default: return ""; } } // Convert t_ext_support to an index in the SIP extension combo box int UserProfileForm::ext_support2indexComboItem(t_ext_support ext) { switch(ext) { case EXT_DISABLED: return idxExtDisabled; case EXT_SUPPORTED: return idxExtSupported; case EXT_REQUIRED: return idxExtRequired; case EXT_PREFERRED: return idxExtPreferred; default: return idxExtDisabled; } return idxExtDisabled; } t_ext_support UserProfileForm::indexComboItem2ext_support(int index) { switch(index) { case idxExtDisabled: return EXT_DISABLED; case idxExtSupported: return EXT_SUPPORTED; case idxExtRequired: return EXT_REQUIRED; case idxExtPreferred: return EXT_PREFERRED; } return EXT_DISABLED; } // Populate the form void UserProfileForm::populate() { QString s; // Set user profile name in the titlebar s = PRODUCT_NAME; s.append(" - ").append(tr("User profile:")).append(" "); s.append(current_profile->get_profile_name().c_str()); setWindowTitle(s); // Select the User category categoryListBox->setCurrentRow(idxCatUser); settingsWidgetStack->setCurrentWidget(pageUser); // Set focus on first field displayLineEdit->setFocus(); // Set the values of the current_profile object in the form // USER displayLineEdit->setText(current_profile->get_display(false).c_str()); usernameLineEdit->setText(current_profile->get_name().c_str()); domainLineEdit->setText(current_profile->get_domain().c_str()); organizationLineEdit->setText(current_profile->get_organization().c_str()); authRealmLineEdit->setText(current_profile->get_auth_realm().c_str()); authNameLineEdit->setText(current_profile->get_auth_name().c_str()); authPasswordLineEdit->setText(current_profile->get_auth_pass().c_str()); uint8 aka_op[AKA_OPLEN]; current_profile->get_auth_aka_op(aka_op); authAkaOpLineEdit->setText(binary2hex(aka_op, AKA_OPLEN).c_str()); uint8 aka_amf[AKA_AMFLEN]; current_profile->get_auth_aka_amf(aka_amf); authAkaAmfLineEdit->setText(binary2hex(aka_amf, AKA_AMFLEN).c_str()); // SIP SERVER registrarLineEdit->setText(current_profile->get_registrar().encode_noscheme().c_str()); expirySpinBox->setValue(current_profile->get_registration_time()); regAtStartupCheckBox->setChecked(current_profile->get_register_at_startup()); regAddQvalueCheckBox->setChecked(current_profile->get_reg_add_qvalue()); regQvalueLineEdit->setEnabled(current_profile->get_reg_add_qvalue()); regQvalueLineEdit->setText(float2str(current_profile->get_reg_qvalue(), 3).c_str()); useProxyCheckBox->setChecked(current_profile->get_use_outbound_proxy()); proxyTextLabel->setEnabled(current_profile->get_use_outbound_proxy()); proxyLineEdit->setEnabled(current_profile->get_use_outbound_proxy()); if (current_profile->get_use_outbound_proxy()) { proxyLineEdit->setText(current_profile-> get_outbound_proxy().encode_noscheme().c_str()); } else { proxyLineEdit->clear(); } allRequestsCheckBox->setChecked(current_profile->get_all_requests_to_proxy()); allRequestsCheckBox->setEnabled(current_profile->get_use_outbound_proxy()); proxyNonResolvableCheckBox->setChecked(current_profile->get_non_resolvable_to_proxy()); proxyNonResolvableCheckBox->setEnabled(current_profile->get_use_outbound_proxy()); // VOICE MAIL vmAddressLineEdit->setText(current_profile->get_mwi_vm_address().c_str()); if (current_profile->get_mwi_sollicited()) { mwiTypeComboBox->setCurrentIndex(idxMWISollicited); mwiSollicitedGroupBox->setEnabled(true); } else { mwiTypeComboBox->setCurrentIndex(idxMWIUnsollicited); mwiSollicitedGroupBox->setEnabled(false); } mwiUserLineEdit->setText(current_profile->get_mwi_user().c_str()); mwiServerLineEdit->setText(current_profile-> get_mwi_server().encode_noscheme().c_str()); mwiViaProxyCheckBox->setChecked(current_profile->get_mwi_via_proxy()); mwiDurationSpinBox->setValue(current_profile->get_mwi_subscription_time()); // INSTANT MESSAGE imMaxSessionsSpinBox->setValue(current_profile->get_im_max_sessions()); isComposingCheckBox->setChecked(current_profile->get_im_send_iscomposing()); // PRESENCE presPublishCheckBox->setChecked(current_profile->get_pres_publish_startup()); presPublishTimeSpinBox->setValue(current_profile->get_pres_publication_time()); presSubscribeTimeSpinBox->setValue(current_profile->get_pres_subscription_time()); // RTP AUDIO // Codecs QStringList allCodecs; allCodecs.append(labelCodecG711a); allCodecs.append(labelCodecG711u); allCodecs.append(labelCodecGSM); #ifdef HAVE_SPEEX allCodecs.append(labelCodecSpeexNb); allCodecs.append(labelCodecSpeexWb); allCodecs.append(labelCodecSpeexUwb); #endif #ifdef HAVE_ILBC allCodecs.append(labelCodecIlbc); #endif allCodecs.append(labelCodecG726_16); allCodecs.append(labelCodecG726_24); allCodecs.append(labelCodecG726_32); allCodecs.append(labelCodecG726_40); #ifdef HAVE_BCG729 allCodecs.append(labelCodecG729A); #endif activeCodecListBox->clear(); list audio_codecs = current_profile->get_codecs(); for (list::iterator i = audio_codecs.begin(); i != audio_codecs.end(); i++) { activeCodecListBox->addItem(codec2label(*i)); allCodecs.removeAll(codec2label(*i)); } availCodecListBox->clear(); if (!allCodecs.empty()) availCodecListBox->addItems(allCodecs); // G.711/G.726 ptime ptimeSpinBox->setValue(current_profile->get_ptime()); // Codec preference inFarEndCodecPrefCheckBox->setChecked(current_profile->get_in_obey_far_end_codec_pref()); outFarEndCodecPrefCheckBox->setChecked(current_profile->get_out_obey_far_end_codec_pref()); // Speex preprocessing and AEC spxDspVadCheckBox->setChecked(current_profile->get_speex_dsp_vad()); spxDspAgcCheckBox->setChecked(current_profile->get_speex_dsp_agc()); spxDspAecCheckBox->setChecked(current_profile->get_speex_dsp_aec()); spxDspNrdCheckBox->setChecked(current_profile->get_speex_dsp_nrd()); spxDspAgcLevelSpinBox->setValue(current_profile->get_speex_dsp_agc_level()); spxDspAgcLevelTextLabel->setEnabled(current_profile->get_speex_dsp_agc()); spxDspAgcLevelSpinBox->setEnabled(current_profile->get_speex_dsp_agc()); // Speex ([en/de]coding) spxVbrCheckBox->setChecked(current_profile->get_speex_bit_rate_type() == BIT_RATE_VBR); spxDtxCheckBox->setChecked(current_profile->get_speex_dtx()); spxPenhCheckBox->setChecked(current_profile->get_speex_penh()); spxQualitySpinBox->setValue(current_profile->get_speex_quality()); spxComplexitySpinBox->setValue(current_profile->get_speex_complexity()); spxNbPayloadSpinBox->setValue(current_profile->get_speex_nb_payload_type()); spxWbPayloadSpinBox->setValue(current_profile->get_speex_wb_payload_type()); spxUwbPayloadSpinBox->setValue(current_profile->get_speex_uwb_payload_type()); // iLBC ilbcPayloadSpinBox->setValue(current_profile->get_ilbc_payload_type()); if (current_profile->get_ilbc_mode() == 20) { ilbcPayloadSizeComboBox->setCurrentIndex(idxIlbcMode20); } else { ilbcPayloadSizeComboBox->setCurrentIndex(idxIlbcMode30); } // G.726 g72616PayloadSpinBox->setValue(current_profile->get_g726_16_payload_type()); g72624PayloadSpinBox->setValue(current_profile->get_g726_24_payload_type()); g72632PayloadSpinBox->setValue(current_profile->get_g726_32_payload_type()); g72640PayloadSpinBox->setValue(current_profile->get_g726_40_payload_type()); if (current_profile->get_g726_packing() == G726_PACK_RFC3551) { g726PackComboBox->setCurrentIndex(idxG726PackRfc3551); } else { g726PackComboBox->setCurrentIndex(idxG726PackAal2); } // DTMF switch (current_profile->get_dtmf_transport()) { case DTMF_RFC2833: dtmfTransportComboBox->setCurrentIndex(idxDtmfRfc2833); break; case DTMF_INBAND: dtmfTransportComboBox->setCurrentIndex(idxDtmfInband); break; case DTMF_INFO: dtmfTransportComboBox->setCurrentIndex(idxDtmfInfo); break; default: dtmfTransportComboBox->setCurrentIndex(idxDtmfAuto); break; } dtmfPayloadTypeSpinBox->setValue(current_profile->get_dtmf_payload_type()); dtmfDurationSpinBox->setValue(current_profile->get_dtmf_duration()); dtmfPauseSpinBox->setValue(current_profile->get_dtmf_pause()); dtmfVolumeSpinBox->setValue(-(current_profile->get_dtmf_volume())); // SIP PROTOCOL switch (current_profile->get_hold_variant()) { case HOLD_RFC2543: holdVariantComboBox->setCurrentIndex(idxHoldRfc2543); break; default: holdVariantComboBox->setCurrentIndex(idxHoldRfc3264); break; } maxForwardsCheckBox->setChecked(current_profile->get_check_max_forwards()); missingContactCheckBox->setChecked(current_profile->get_allow_missing_contact_reg()); regTimeCheckBox->setChecked(current_profile->get_registration_time_in_contact()); compactHeadersCheckBox->setChecked(current_profile->get_compact_headers()); multiValuesListCheckBox->setChecked( current_profile->get_encode_multi_values_as_list()); useDomainInContactCheckBox->setChecked( current_profile->get_use_domain_in_contact()); allowSdpChangeCheckBox->setChecked(current_profile->get_allow_sdp_change()); allowRedirectionCheckBox->setChecked(current_profile->get_allow_redirection()); askUserRedirectCheckBox->setEnabled(current_profile->get_allow_redirection()); askUserRedirectCheckBox->setChecked(current_profile->get_ask_user_to_redirect()); maxRedirectTextLabel->setEnabled(current_profile->get_allow_redirection()); maxRedirectSpinBox->setEnabled(current_profile->get_allow_redirection()); maxRedirectSpinBox->setValue(current_profile->get_max_redirections()); ext100relComboBox->setCurrentIndex( ext_support2indexComboItem(current_profile->get_ext_100rel())); extReplacesCheckBox->setChecked(current_profile->get_ext_replaces()); allowReferCheckBox->setChecked(current_profile->get_allow_refer()); askUserReferCheckBox->setEnabled(current_profile->get_allow_refer()); askUserReferCheckBox->setChecked(current_profile->get_ask_user_to_refer()); refereeHoldCheckBox->setEnabled(current_profile->get_allow_refer()); refereeHoldCheckBox->setChecked(current_profile->get_referee_hold()); referrerHoldCheckBox->setChecked(current_profile->get_referrer_hold()); refreshReferSubCheckBox->setChecked(current_profile->get_auto_refresh_refer_sub()); referAorCheckBox->setChecked(current_profile->get_attended_refer_to_aor()); transferConsultInprogCheckBox->setChecked( current_profile->get_allow_transfer_consultation_inprog()); pPreferredIdCheckBox->setChecked(current_profile->get_send_p_preferred_id()); // Transport/NAT switch (current_profile->get_sip_transport()) { case SIP_TRANS_UDP: sipTransportComboBox->setCurrentIndex(idxSipTransportUDP); break; case SIP_TRANS_TCP: sipTransportComboBox->setCurrentIndex(idxSipTransportTCP); break; default: sipTransportComboBox->setCurrentIndex(idxSipTransportAuto); break; } udpThresholdSpinBox->setValue(current_profile->get_sip_transport_udp_threshold()); udpThresholdTextLabel->setEnabled(current_profile->get_sip_transport() == SIP_TRANS_AUTO); udpThresholdSpinBox->setEnabled(current_profile->get_sip_transport() == SIP_TRANS_AUTO); if (current_profile->get_use_nat_public_ip()) { natStaticRadioButton->setChecked(true); } else if (current_profile->get_use_stun()) { natStunRadioButton->setChecked(true); } else { natNoneRadioButton->setChecked(true); } publicIPTextLabel->setEnabled(current_profile->get_use_nat_public_ip()); publicIPLineEdit->setEnabled(current_profile->get_use_nat_public_ip()); publicIPLineEdit->setText(current_profile->get_nat_public_ip().c_str()); stunServerTextLabel->setEnabled(current_profile->get_use_stun()); stunServerLineEdit->setEnabled(current_profile->get_use_stun()); stunServerLineEdit->setText(current_profile->get_stun_server(). encode_noscheme().c_str()); persistentTcpCheckBox->setChecked(current_profile->get_persistent_tcp()); persistentTcpCheckBox->setEnabled(current_profile->get_sip_transport() == SIP_TRANS_TCP); natKeepaliveCheckBox->setChecked(current_profile->get_enable_nat_keepalive()); natKeepaliveCheckBox->setDisabled(current_profile->get_use_stun()); // ADDRESS FORMAT displayTelUserCheckBox->setChecked(current_profile->get_display_useronly_phone()); numericalUserIsTelCheckBox->setChecked( current_profile->get_numerical_user_is_phone()); removeSpecialCheckBox->setChecked( current_profile->get_remove_special_phone_symbols()); specialLineEdit->setText(current_profile->get_special_phone_symbols().c_str()); useTelUriCheckBox->setChecked(current_profile->get_use_tel_uri_for_phone()); list conversions = current_profile->get_number_conversions(); conversionListView->horizontalHeader()->resizeSection(0, 200); conversionListView->setRowCount(conversions.size()); int j = 0; for (list::iterator i = conversions.begin(); i != conversions.end(); i++, j++) { QTableWidgetItem* item = new QTableWidgetItem(QString::fromStdString(i->re)); conversionListView->setItem(j, 0, item); item = new QTableWidgetItem(QString::fromStdString(i->fmt)); conversionListView->setItem(j, 1, item); } // TIMERS tmrNoanswerSpinBox->setValue(current_profile->get_timer_noanswer()); tmrNatKeepaliveSpinBox->setValue(current_profile->get_timer_nat_keepalive()); // RING TONES ringtoneLineEdit->setText(current_profile->get_ringtone_file().c_str()); ringbackLineEdit->setText(current_profile->get_ringback_file().c_str()); // SCRIPTS incomingCallScriptLineEdit->setText(current_profile->get_script_incoming_call().c_str()); inCallAnsweredLineEdit->setText(current_profile->get_script_in_call_answered().c_str()); inCallFailedLineEdit->setText(current_profile->get_script_in_call_failed().c_str()); outCallLineEdit->setText(current_profile->get_script_outgoing_call().c_str()); outCallAnsweredLineEdit->setText(current_profile->get_script_out_call_answered().c_str()); outCallFailedLineEdit->setText(current_profile->get_script_out_call_failed().c_str()); localReleaseLineEdit->setText(current_profile->get_script_local_release().c_str()); remoteReleaseLineEdit->setText(current_profile->get_script_remote_release().c_str()); // Security zrtpEnabledCheckBox->setChecked(current_profile->get_zrtp_enabled()); zrtpSettingsGroupBox->setEnabled(current_profile->get_zrtp_enabled()); zrtpSendIfSupportedCheckBox->setChecked(current_profile->get_zrtp_send_if_supported()); zrtpSdpCheckBox->setChecked(current_profile->get_zrtp_sdp()); zrtpGoClearWarningCheckBox->setChecked(current_profile->get_zrtp_goclear_warning()); } void UserProfileForm::initProfileList(list profiles, QString show_profile_name) { profile_list = profiles; // Initialize user profile combo box current_profile_idx = -1; profileComboBox->clear(); t_user *show_profile = NULL; int show_idx = 0; int idx = 0; for (list::iterator i = profile_list.begin(); i != profile_list.end(); i++) { profileComboBox->addItem((*i)->get_profile_name().c_str()); if (show_profile_name == (*i)->get_profile_name().c_str()) { show_idx = idx; show_profile = *i; } idx++; } profileComboBox->setEnabled(profile_list.size() > 1); current_profile_idx = show_idx; if (show_profile == NULL) { current_profile = profile_list.front(); } else { current_profile = show_profile; } profileComboBox->setCurrentIndex(current_profile_idx); } // Show the form void UserProfileForm::show(list profiles, QString show_profile) { map_last_cat.clear(); initProfileList(profiles, show_profile); populate(); // Show form QDialog::show(); } // Modal execution int UserProfileForm::exec(list profiles, QString show_profile) { map_last_cat.clear(); initProfileList(profiles, show_profile); populate(); return QDialog::exec(); } bool UserProfileForm::check_dynamic_payload(QSpinBox *spb, QList &checked_list) { if (checked_list.contains(spb->value())) { categoryListBox->setCurrentRow(idxCatRtpAudio); settingsWidgetStack->setCurrentWidget(pageRtpAudio); QString msg = tr("Dynamic payload type %1 is used more than once.").arg(spb->value()); ((t_gui *)ui)->cb_show_msg(this, msg.toStdString(), MSG_CRITICAL); spb->setFocus(); return false; } checked_list << spb->value(); return true; } list UserProfileForm::get_number_conversions() { list conversions; for (int i = 0; i < conversionListView->rowCount(); i++) { QTableWidgetItem* item; t_number_conversion c; try { item = conversionListView->item(i, 0); c.re.assign(item->text().toStdString()); item = conversionListView->item(i, 1); c.fmt = item->text().toStdString(); conversions.push_back(c); } catch (std::regex_error) { // Should never happen as validity has been // checked already. Just being defensive here. } } return conversions; } bool UserProfileForm::validateValues() { QString s; // Validity check user page // SIP username is mandatory if (usernameLineEdit->text().isEmpty()) { categoryListBox->setCurrentRow(idxCatUser); settingsWidgetStack->setCurrentWidget(pageUser); ((t_gui *)ui)->cb_show_msg(this, tr("You must fill in a user name for your SIP account.").toStdString(), MSG_CRITICAL); usernameLineEdit->setFocus(); return false; } // SIP user domain is mandatory if (domainLineEdit->text().isEmpty()) { categoryListBox->setCurrentRow(idxCatUser); settingsWidgetStack->setCurrentWidget(pageUser); ((t_gui *)ui)->cb_show_msg(this, tr( "You must fill in a domain name for your SIP account.\n" "This could be the hostname or IP address of your PC " "if you want direct PC to PC dialing.").toStdString(), MSG_CRITICAL); domainLineEdit->setFocus(); return false; } // Check validity of domain s = USER_SCHEME; s.append(':').append(domainLineEdit->text()); t_url u_domain(s.toStdString()); if (!u_domain.is_valid() || u_domain.get_user() != "") { categoryListBox->setCurrentRow(idxCatUser); settingsWidgetStack->setCurrentWidget(pageUser); ((t_gui *)ui)->cb_show_msg(this, tr("Invalid domain.").toStdString(), MSG_CRITICAL); domainLineEdit->setFocus(); return false; } // Check validity of user s = USER_SCHEME; s.append(':').append(usernameLineEdit->text()).append('@'); s.append(domainLineEdit->text()); t_url u_user_domain(s.toStdString()); if (!u_user_domain.is_valid()) { categoryListBox->setCurrentRow(idxCatUser); settingsWidgetStack->setCurrentWidget(pageUser); ((t_gui *)ui)->cb_show_msg(this, tr("Invalid user name.").toStdString(), MSG_CRITICAL); usernameLineEdit->setFocus(); return false; } // Registrar if (!registrarLineEdit->text().isEmpty()) { s = USER_SCHEME; s.append(':').append(registrarLineEdit->text()); t_url u(s.toStdString()); if (!u.is_valid() || u.get_user() != "") { categoryListBox->setCurrentRow(idxCatSipServer); settingsWidgetStack->setCurrentWidget(pageSipServer); ((t_gui *)ui)->cb_show_msg(this, tr("Invalid value for registrar.").toStdString(), MSG_CRITICAL); registrarLineEdit->setFocus(); registrarLineEdit->selectAll(); return false; } } // Outbound proxy if (useProxyCheckBox->isChecked()) { s = USER_SCHEME; s.append(':').append(proxyLineEdit->text()); t_url u(s.toStdString()); if (!u.is_valid() || u.get_user() != "") { categoryListBox->setCurrentRow(idxCatSipServer); settingsWidgetStack->setCurrentWidget(pageSipServer); ((t_gui *)ui)->cb_show_msg(this, tr("Invalid value for outbound proxy.").toStdString(), MSG_CRITICAL); proxyLineEdit->setFocus(); proxyLineEdit->selectAll(); return false; } } // Validity check voice mail page if (mwiTypeComboBox->currentIndex() == idxMWISollicited) { // Mailbox user name is mandatory if (mwiUserLineEdit->text().isEmpty()) { categoryListBox->setCurrentRow(idxCatVoiceMail); settingsWidgetStack->setCurrentWidget(pageVoiceMail); ((t_gui *)ui)->cb_show_msg(this, tr("You must fill in a mailbox user name.").toStdString(), MSG_CRITICAL); mwiUserLineEdit->setFocus(); return false; } // Mailbox server is mandatory if (mwiServerLineEdit->text().isEmpty()) { categoryListBox->setCurrentRow(idxCatVoiceMail); settingsWidgetStack->setCurrentWidget(pageVoiceMail); ((t_gui *)ui)->cb_show_msg(this, tr("You must fill in a mailbox server").toStdString(), MSG_CRITICAL); mwiServerLineEdit->setFocus(); return false; } // Check validity of mailbox server s = USER_SCHEME; s.append(':').append(mwiServerLineEdit->text()); t_url u_server(s.toStdString()); if (!u_server.is_valid() || u_server.get_user() != "") { categoryListBox->setCurrentRow(idxCatVoiceMail); settingsWidgetStack->setCurrentWidget(pageVoiceMail); ((t_gui *)ui)->cb_show_msg(this, tr("Invalid mailbox server.").toStdString(), MSG_CRITICAL); mwiServerLineEdit->setFocus(); return false; } // Check validity of mailbox user name s = USER_SCHEME; s.append(':').append(mwiUserLineEdit->text()).append('@'); s.append(mwiServerLineEdit->text()); t_url u_user_server(s.toStdString()); if (!u_user_server.is_valid()) { categoryListBox->setCurrentRow(idxCatVoiceMail); settingsWidgetStack->setCurrentWidget(pageVoiceMail); ((t_gui *)ui)->cb_show_msg(this, tr("Invalid mailbox user name.").toStdString(), MSG_CRITICAL); mwiUserLineEdit->setFocus(); return false; } } // NAT public IP if (natStaticRadioButton->isChecked()) { if (publicIPLineEdit->text().isEmpty()){ categoryListBox->setCurrentRow(idxCatNat); settingsWidgetStack->setCurrentWidget(pageNat); ((t_gui *)ui)->cb_show_msg(this, tr("Value for public IP address missing.").toStdString(), MSG_CRITICAL); publicIPLineEdit->setFocus(); return false; } } // Check for double RTP dynamic payload types QList checked_types; if (!check_dynamic_payload(spxNbPayloadSpinBox, checked_types) || !check_dynamic_payload(spxWbPayloadSpinBox, checked_types) || !check_dynamic_payload(spxUwbPayloadSpinBox, checked_types)) { rtpAudioTabWidget->setCurrentWidget(tabSpeex); return false; } if (!check_dynamic_payload(ilbcPayloadSpinBox, checked_types)) { rtpAudioTabWidget->setCurrentWidget(tabIlbc); return false; } if (!check_dynamic_payload(g72616PayloadSpinBox, checked_types) || !check_dynamic_payload(g72624PayloadSpinBox, checked_types) || !check_dynamic_payload(g72632PayloadSpinBox, checked_types) || !check_dynamic_payload(g72640PayloadSpinBox, checked_types)) { rtpAudioTabWidget->setCurrentWidget(tabG726); return false; } if (!check_dynamic_payload(dtmfPayloadTypeSpinBox, checked_types)) { rtpAudioTabWidget->setCurrentWidget(tabDtmf); return false; } // STUN server if (natStunRadioButton->isChecked()) { s = "stun:"; s.append(stunServerLineEdit->text()); t_url u(s.toStdString()); if (!u.is_valid() || u.get_user() != "") { categoryListBox->setCurrentRow(idxCatNat); settingsWidgetStack->setCurrentWidget(pageNat); ((t_gui *)ui)->cb_show_msg(this, tr("Invalid value for STUN server.").toStdString(), MSG_CRITICAL); stunServerLineEdit->setFocus(); stunServerLineEdit->selectAll(); return false; } } // Clear outbound proxy if not used if (!useProxyCheckBox->isChecked()) { proxyLineEdit->clear(); } // Clear sollicited MWI settings if unsollicited MWI is used if (mwiTypeComboBox->currentIndex() == idxMWIUnsollicited) { t_user user_default; mwiUserLineEdit->clear(); mwiServerLineEdit->clear(); mwiViaProxyCheckBox->setChecked(user_default.get_mwi_via_proxy()); mwiDurationSpinBox->setValue(user_default.get_mwi_subscription_time()); } // Clear NAT public IP if not used if (!natStaticRadioButton->isChecked()) { publicIPLineEdit->clear(); } // Clear STUN server if not used if (!natStunRadioButton->isChecked()) { stunServerLineEdit->clear(); } // Set all values in the current_profile object // USER if (current_profile->get_name() != usernameLineEdit->text().toStdString() || current_profile->get_display(false) != displayLineEdit->text().toStdString() || current_profile->get_domain() != domainLineEdit->text().toStdString()) { current_profile->set_display(displayLineEdit->text().toStdString()); current_profile->set_name(usernameLineEdit->text().toStdString()); current_profile->set_domain (domainLineEdit->text().toStdString()); emit sipUserChanged(current_profile); } current_profile->set_organization(organizationLineEdit->text().toStdString()); uint8 new_aka_op[AKA_OPLEN]; uint8 new_aka_amf[AKA_AMFLEN]; uint8 current_aka_op[AKA_OPLEN]; uint8 current_aka_amf[AKA_AMFLEN]; hex2binary(padleft(authAkaOpLineEdit->text().toStdString(), '0', 32), new_aka_op); hex2binary(padleft(authAkaAmfLineEdit->text().toStdString(), '0', 4), new_aka_amf); current_profile->get_auth_aka_op(current_aka_op); current_profile->get_auth_aka_amf(current_aka_amf); if (current_profile->get_auth_realm() != authRealmLineEdit->text().toStdString() || current_profile->get_auth_name() != authNameLineEdit->text().toStdString() || current_profile->get_auth_pass() != authPasswordLineEdit->text().toStdString() || memcmp(current_aka_op, new_aka_op, AKA_OPLEN) != 0 || memcmp(current_aka_amf, new_aka_amf, AKA_AMFLEN) != 0) { emit authCredentialsChanged(current_profile, current_profile->get_auth_realm()); current_profile->set_auth_realm(authRealmLineEdit->text().toStdString()); current_profile->set_auth_name(authNameLineEdit->text().toStdString()); current_profile->set_auth_pass(authPasswordLineEdit->text().toStdString()); current_profile->set_auth_aka_op(new_aka_op); current_profile->set_auth_aka_amf(new_aka_amf); } // SIP SERVER current_profile->set_use_registrar(!registrarLineEdit->text().isEmpty()); s = USER_SCHEME; s.append(':').append(registrarLineEdit->text()); current_profile->set_registrar(t_url(s.toStdString())); current_profile->set_registration_time(expirySpinBox->value()); current_profile->set_register_at_startup(regAtStartupCheckBox->isChecked()); current_profile->set_reg_add_qvalue(regAddQvalueCheckBox->isChecked()); current_profile->set_reg_qvalue(regQvalueLineEdit->text().toDouble()); current_profile->set_use_outbound_proxy(useProxyCheckBox->isChecked()); s = USER_SCHEME; s.append(':').append(proxyLineEdit->text()); current_profile->set_outbound_proxy(t_url(s.toStdString())); current_profile->set_all_requests_to_proxy(allRequestsCheckBox->isChecked()); current_profile->set_non_resolvable_to_proxy( proxyNonResolvableCheckBox->isChecked()); // VOICE MAIL current_profile->set_mwi_vm_address(vmAddressLineEdit->text().toStdString()); bool mustTriggerMWISubscribe = false; bool mwiSollicited = (mwiTypeComboBox->currentIndex() == idxMWISollicited); if (mwiSollicited) { if (!current_profile->get_mwi_sollicited()) { // Sollicited MWI now enabled. Subscribe after all MWI // settings have been changed. mustTriggerMWISubscribe = true; } else { s = USER_SCHEME; s.append(':').append(mwiServerLineEdit->text()); if (mwiUserLineEdit->text().toStdString() != current_profile->get_mwi_user() || t_url(s.toStdString()) != current_profile->get_mwi_server() || mwiViaProxyCheckBox->isChecked() != current_profile->get_mwi_via_proxy()) { // Sollicited MWI settings changed. Trigger unsubscribe // of current MWI subscription. emit mwiChangeUnsubscribe(current_profile); // Subscribe after the settings have been changed. mustTriggerMWISubscribe = true; } } } else { if (current_profile->get_mwi_sollicited()) { // MWI type changes to unsollicited. Trigger unsubscribe of // current MWI subscription. emit mwiChangeUnsubscribe(current_profile); } } current_profile->set_mwi_sollicited(mwiSollicited); current_profile->set_mwi_user(mwiUserLineEdit->text().toStdString()); s = USER_SCHEME; s.append(':').append(mwiServerLineEdit->text()); current_profile->set_mwi_server(t_url(s.toStdString())); current_profile->set_mwi_via_proxy(mwiViaProxyCheckBox->isChecked()); current_profile->set_mwi_subscription_time(mwiDurationSpinBox->value()); if (mustTriggerMWISubscribe) { emit mwiChangeSubscribe(current_profile); } // INSTANT MESSAGE current_profile->set_im_max_sessions(imMaxSessionsSpinBox->value()); current_profile->set_im_send_iscomposing(isComposingCheckBox->isChecked()); // PRESENCE current_profile->set_pres_publish_startup(presPublishCheckBox->isChecked()); current_profile->set_pres_publication_time(presPublishTimeSpinBox->value()); current_profile->set_pres_subscription_time(presSubscribeTimeSpinBox->value()); // RTP AUDIO // Codecs list audio_codecs; for (int i = 0; i < activeCodecListBox->count(); i++) { audio_codecs.push_back(label2codec(activeCodecListBox->item(i)->text())); } current_profile->set_codecs(audio_codecs); // G.711/G.726 ptime current_profile->set_ptime(ptimeSpinBox->value()); // Codec preference current_profile->set_in_obey_far_end_codec_pref(inFarEndCodecPrefCheckBox->isChecked()); current_profile->set_out_obey_far_end_codec_pref(outFarEndCodecPrefCheckBox->isChecked()); // Speex preprocessing & AEC current_profile->set_speex_dsp_vad(spxDspVadCheckBox->isChecked()); current_profile->set_speex_dsp_agc(spxDspAgcCheckBox->isChecked()); current_profile->set_speex_dsp_aec(spxDspAecCheckBox->isChecked()); current_profile->set_speex_dsp_nrd(spxDspNrdCheckBox->isChecked()); current_profile->set_speex_dsp_agc_level(spxDspAgcLevelSpinBox->value()); // Speex ([en/de]coding) current_profile->set_speex_bit_rate_type((spxVbrCheckBox->isChecked() ? BIT_RATE_VBR : BIT_RATE_CBR)); current_profile->set_speex_dtx(spxDtxCheckBox->isChecked()); current_profile->set_speex_penh(spxPenhCheckBox->isChecked()); current_profile->set_speex_quality(spxQualitySpinBox->value()); current_profile->set_speex_complexity(spxComplexitySpinBox->value()); current_profile->set_speex_nb_payload_type(spxNbPayloadSpinBox->value()); current_profile->set_speex_wb_payload_type(spxWbPayloadSpinBox->value()); current_profile->set_speex_uwb_payload_type(spxUwbPayloadSpinBox->value()); // iLBC current_profile->set_ilbc_payload_type(ilbcPayloadSpinBox->value()); switch (ilbcPayloadSizeComboBox->currentIndex()) { case idxIlbcMode20: current_profile->set_ilbc_mode(20); break; default: current_profile->set_ilbc_mode(30); break; } // G726 current_profile->set_g726_16_payload_type(g72616PayloadSpinBox->value()); current_profile->set_g726_24_payload_type(g72624PayloadSpinBox->value()); current_profile->set_g726_32_payload_type(g72632PayloadSpinBox->value()); current_profile->set_g726_40_payload_type(g72640PayloadSpinBox->value()); switch (g726PackComboBox->currentIndex()) { case idxG726PackRfc3551: current_profile->set_g726_packing(G726_PACK_RFC3551); break; default: current_profile->set_g726_packing(G726_PACK_AAL2); break; } // DTMF switch (dtmfTransportComboBox->currentIndex()) { case idxDtmfRfc2833: current_profile->set_dtmf_transport(DTMF_RFC2833); break; case idxDtmfInband: current_profile->set_dtmf_transport(DTMF_INBAND); break; case idxDtmfInfo: current_profile->set_dtmf_transport(DTMF_INFO); break; default: current_profile->set_dtmf_transport(DTMF_AUTO); break; } current_profile->set_dtmf_payload_type(dtmfPayloadTypeSpinBox->value()); current_profile->set_dtmf_duration(dtmfDurationSpinBox->value()); current_profile->set_dtmf_pause(dtmfPauseSpinBox->value()); current_profile->set_dtmf_volume(-(dtmfVolumeSpinBox->value())); // SIP PROTOCOL switch (holdVariantComboBox->currentIndex()) { case idxHoldRfc2543: current_profile->set_hold_variant(HOLD_RFC2543); break; default: current_profile->set_hold_variant(HOLD_RFC3264); break; } current_profile->set_check_max_forwards(maxForwardsCheckBox->isChecked()); current_profile->set_allow_missing_contact_reg(missingContactCheckBox->isChecked()); current_profile->set_registration_time_in_contact(regTimeCheckBox->isChecked()); current_profile->set_compact_headers(compactHeadersCheckBox->isChecked()); current_profile->set_encode_multi_values_as_list( multiValuesListCheckBox->isChecked()); current_profile->set_use_domain_in_contact( useDomainInContactCheckBox->isChecked()); current_profile->set_allow_sdp_change(allowSdpChangeCheckBox->isChecked()); current_profile->set_allow_redirection(allowRedirectionCheckBox->isChecked()); current_profile->set_ask_user_to_redirect(askUserRedirectCheckBox->isChecked()); current_profile->set_max_redirections(maxRedirectSpinBox->value()); current_profile->set_ext_100rel(indexComboItem2ext_support( ext100relComboBox->currentIndex())); current_profile->set_ext_replaces(extReplacesCheckBox->isChecked()); current_profile->set_allow_refer(allowReferCheckBox->isChecked()); current_profile->set_ask_user_to_refer(askUserReferCheckBox->isChecked()); current_profile->set_referee_hold(refereeHoldCheckBox->isChecked()); current_profile->set_referrer_hold(referrerHoldCheckBox->isChecked()); current_profile->set_auto_refresh_refer_sub(refreshReferSubCheckBox->isChecked()); current_profile->set_attended_refer_to_aor(referAorCheckBox->isChecked()); current_profile->set_allow_transfer_consultation_inprog( transferConsultInprogCheckBox->isChecked()); current_profile->set_send_p_preferred_id(pPreferredIdCheckBox->isChecked()); // Transport/NAT switch (sipTransportComboBox->currentIndex()) { case idxSipTransportUDP: current_profile->set_sip_transport(SIP_TRANS_UDP); break; case idxSipTransportTCP: current_profile->set_sip_transport(SIP_TRANS_TCP); break; default: current_profile->set_sip_transport(SIP_TRANS_AUTO); break; } current_profile->set_sip_transport_udp_threshold(udpThresholdSpinBox->value()); current_profile->set_use_nat_public_ip(natStaticRadioButton->isChecked()); current_profile->set_nat_public_ip(publicIPLineEdit->text().toStdString()); current_profile->set_use_stun(natStunRadioButton->isChecked()); if (current_profile->get_stun_server().encode_noscheme() != stunServerLineEdit->text().toStdString() || current_profile->get_enable_nat_keepalive() != natKeepaliveCheckBox->isChecked()) { s = "stun:"; s.append(stunServerLineEdit->text()); current_profile->set_stun_server(t_url(s.toStdString())); current_profile->set_enable_nat_keepalive(natKeepaliveCheckBox->isChecked()); emit stunServerChanged(current_profile); } current_profile->set_persistent_tcp(persistentTcpCheckBox->isChecked()); // ADDRESS FORMAT current_profile->set_display_useronly_phone( displayTelUserCheckBox->isChecked()); current_profile->set_numerical_user_is_phone( numericalUserIsTelCheckBox->isChecked()); current_profile->set_remove_special_phone_symbols( removeSpecialCheckBox->isChecked()); current_profile->set_special_phone_symbols( specialLineEdit->text().trimmed().toStdString()); current_profile->set_number_conversions(get_number_conversions()); current_profile->set_use_tel_uri_for_phone(useTelUriCheckBox->isChecked()); // TIMERS current_profile->set_timer_noanswer(tmrNoanswerSpinBox->value()); current_profile->set_timer_nat_keepalive(tmrNatKeepaliveSpinBox->value()); // RING TONES current_profile->set_ringtone_file(ringtoneLineEdit->text().trimmed().toStdString()); current_profile->set_ringback_file(ringbackLineEdit->text().trimmed().toStdString()); // SCRIPTS current_profile->set_script_incoming_call(incomingCallScriptLineEdit-> text().trimmed().toStdString()); current_profile->set_script_in_call_answered(inCallAnsweredLineEdit-> text().trimmed().toStdString()); current_profile->set_script_in_call_failed(inCallFailedLineEdit-> text().trimmed().toStdString()); current_profile->set_script_outgoing_call(outCallLineEdit-> text().trimmed().toStdString()); current_profile->set_script_out_call_answered(outCallAnsweredLineEdit-> text().trimmed().toStdString()); current_profile->set_script_out_call_failed(outCallFailedLineEdit-> text().trimmed().toStdString()); current_profile->set_script_local_release(localReleaseLineEdit-> text().trimmed().toStdString()); current_profile->set_script_remote_release(remoteReleaseLineEdit-> text().trimmed().toStdString()); // Security current_profile->set_zrtp_enabled(zrtpEnabledCheckBox->isChecked()); current_profile->set_zrtp_send_if_supported(zrtpSendIfSupportedCheckBox->isChecked()); current_profile->set_zrtp_sdp(zrtpSdpCheckBox->isChecked()); current_profile->set_zrtp_goclear_warning(zrtpGoClearWarningCheckBox->isChecked()); // Save user config string error_msg; if (!current_profile->write_config(current_profile->get_filename(), error_msg)) { // Failed to write config file ((t_gui *)ui)->cb_show_msg(this, error_msg, MSG_CRITICAL); return false; } return true; } void UserProfileForm::validate() { if (validateValues()) { emit success(); accept(); } } // User wants to change to another profile void UserProfileForm::changeProfile(const QString &profileName) { if (current_profile_idx == -1) { // Initializing combo box return; } // Make the current profile permanent. if (!validateValues()) { // Current values are not valid. // Do not change to the new profile. profileComboBox->setCurrentIndex(current_profile_idx); return; } // Store the current viewed category map_last_cat[current_profile] = categoryListBox->currentRow(); // Change to new profile. for (list::iterator i = profile_list.begin(); i != profile_list.end(); i++) { if ((*i)->get_profile_name() == profileName.toStdString()) { current_profile = *i; break; } } current_profile_idx = profileComboBox->currentIndex(); populate(); // Restore last viewed category int idxCat = map_last_cat[current_profile]; categoryListBox->setCurrentRow(idxCat); showCategory(idxCat); } void UserProfileForm::chooseFile(QLineEdit *qle, const QString &filter, const QString &caption) { QString file = QFileDialog::getOpenFileName(this, caption, ((t_gui *)ui)->get_last_file_browse_path(), filter); if (!file.isEmpty()) { qle->setText(file); ((t_gui *)ui)->set_last_file_browse_path(QFileInfo(file).absolutePath()); } } void UserProfileForm::chooseRingtone() { chooseFile(ringtoneLineEdit, tr("Ring tones", "Description of .wav files in file dialog").append(" (*.wav)"), tr("Choose ring tone")); } void UserProfileForm::chooseRingback() { chooseFile(ringbackLineEdit, tr("Ring back tones", "Description of .wav files in file dialog").append(" (*.wav)"), "Choose ring back tone"); } void UserProfileForm::chooseIncomingCallScript() { chooseFile(incomingCallScriptLineEdit, tr("All files").append(" (*)"), tr("Choose incoming call script")); } void UserProfileForm::chooseInCallAnsweredScript() { chooseFile(inCallAnsweredLineEdit, tr("All files").append(" (*)"), tr("Choose incoming call answered script")); } void UserProfileForm::chooseInCallFailedScript() { chooseFile(inCallFailedLineEdit, tr("All files").append(" (*)"), tr("Choose incoming call failed script")); } void UserProfileForm::chooseOutgoingCallScript() { chooseFile(outCallLineEdit, tr("All files").append(" (*)"), tr("Choose outgoing call script")); } void UserProfileForm::chooseOutCallAnsweredScript() { chooseFile(outCallAnsweredLineEdit, tr("All files").append(" (*)"), tr("Choose outgoing call answered script")); } void UserProfileForm::chooseOutCallFailedScript() { chooseFile(outCallFailedLineEdit, tr("All files").append(" (*)"), tr("Choose outgoing call failed script")); } void UserProfileForm::chooseLocalReleaseScript() { chooseFile(localReleaseLineEdit, tr("All files").append(" (*)"), tr("Choose local release script")); } void UserProfileForm::chooseRemoteReleaseScript() { chooseFile(remoteReleaseLineEdit, tr("All files").append(" (*)"), tr("Choose remote release script")); } void UserProfileForm::addCodec() { for (int i = 0; i < availCodecListBox->count(); i++) { if (availCodecListBox->item(i)->isSelected()) { activeCodecListBox->addItem(availCodecListBox->item(i)->text()); activeCodecListBox->item(activeCodecListBox->count()-1)->setSelected(true); delete availCodecListBox->takeItem(i); return; } } } void UserProfileForm::removeCodec() { for (int i = 0; i < activeCodecListBox->count(); i++) { if (activeCodecListBox->item(i)->isSelected()) { availCodecListBox->addItem(activeCodecListBox->item(i)->text()); availCodecListBox->item(availCodecListBox->count() - 1)->setSelected(true); delete activeCodecListBox->takeItem(i); return; } } } void UserProfileForm::upCodec() { int row = activeCodecListBox->currentRow(); if (row <= 0) return; QListWidgetItem* item = activeCodecListBox->takeItem(row); activeCodecListBox->insertItem(row-1, item); activeCodecListBox->setCurrentRow(row-1); } void UserProfileForm::downCodec() { int row = activeCodecListBox->currentRow(); if (row < 0 || row >= activeCodecListBox->count()-1) return; QListWidgetItem* item = activeCodecListBox->takeItem(row); activeCodecListBox->insertItem(row+1, item); activeCodecListBox->setCurrentRow(row+1); } void UserProfileForm::upConversion() { QTableWidgetItem *c1, *c2; QModelIndexList ilist = conversionListView->selectionModel()->selectedRows(); int row; if (ilist.isEmpty()) return; row = ilist[0].row(); if (row == 0) return; c1 = conversionListView->takeItem(row, 0); c2 = conversionListView->takeItem(row, 1); conversionListView->setItem(row, 0, conversionListView->takeItem(row-1, 0)); conversionListView->setItem(row, 1, conversionListView->takeItem(row-1, 1)); conversionListView->setItem(row-1, 0, c1); conversionListView->setItem(row-1, 1, c2); conversionListView->selectRow(row-1); } void UserProfileForm::downConversion() { QTableWidgetItem *c1, *c2; QModelIndexList ilist = conversionListView->selectionModel()->selectedRows(); int row; if (ilist.isEmpty()) return; row = ilist[0].row(); if (row == conversionListView->rowCount()-1) return; c1 = conversionListView->takeItem(row, 0); c2 = conversionListView->takeItem(row, 1); conversionListView->setItem(row, 0, conversionListView->takeItem(row+1, 0)); conversionListView->setItem(row, 1, conversionListView->takeItem(row+1, 1)); conversionListView->setItem(row+1, 0, c1); conversionListView->setItem(row+1, 1, c2); conversionListView->selectRow(row+1); } void UserProfileForm::addConversion() { QString expr; QString replace; NumberConversionForm f; if (f.exec(expr, replace) == QDialog::Accepted) { QTableWidgetItem* item; int row = conversionListView->rowCount(); conversionListView->setRowCount(row + 1); item = new QTableWidgetItem(expr); conversionListView->setItem(row, 0, item); item = new QTableWidgetItem(replace); conversionListView->setItem(row, 1, item); } } void UserProfileForm::editConversion() { QModelIndexList ilist = conversionListView->selectionModel()->selectedRows(); int row; if (ilist.isEmpty()) return; row = ilist[0].row(); QString expr = conversionListView->item(row, 0)->text(); QString replace = conversionListView->item(row, 1)->text(); NumberConversionForm f; if (f.exec(expr, replace) == QDialog::Accepted) { conversionListView->item(row, 0)->setText(expr); conversionListView->item(row, 1)->setText(replace); } } void UserProfileForm::removeConversion() { QModelIndexList ilist = conversionListView->selectionModel()->selectedRows(); int row; if (ilist.isEmpty()) return; row = ilist[0].row(); conversionListView->removeRow(row); } void UserProfileForm::testConversion() { QString number = testConversionLineEdit->text(); if (number.isEmpty()) return; bool remove_special_phone_symbols = removeSpecialCheckBox->isChecked(); QString special_phone_symbols = specialLineEdit->text(); number = remove_white_space(number.toStdString()).c_str(); // Remove special symbols if (remove_special_phone_symbols && looks_like_phone(number.toStdString(), special_phone_symbols.toStdString())) { number = remove_symbols( number.toStdString(), special_phone_symbols.toStdString()).c_str(); } QString msg = tr("%1 converts to %2") .arg(number) .arg(current_profile->convert_number(number.toStdString(), get_number_conversions()).c_str()); ((t_gui *)ui)->cb_show_msg(this, msg.toStdString(), MSG_INFO); } void UserProfileForm::changeMWIType(int idxMWIType) { if (idxMWIType == idxMWISollicited) { mwiSollicitedGroupBox->setEnabled(true); // Set defaults if (mwiUserLineEdit->text().isEmpty()) { mwiUserLineEdit->setText(usernameLineEdit->text()); } if (mwiServerLineEdit->text().isEmpty()) { mwiServerLineEdit->setText(domainLineEdit->text()); mwiViaProxyCheckBox->setChecked(useProxyCheckBox->isChecked()); } } else { mwiSollicitedGroupBox->setEnabled(false); } } void UserProfileForm::changeSipTransportProtocol(int idx) { udpThresholdTextLabel->setEnabled(idx == idxSipTransportAuto); udpThresholdSpinBox->setEnabled(idx == idxSipTransportAuto); persistentTcpCheckBox->setEnabled(idx == idxSipTransportTCP); } twinkle-1.10.1/src/gui/userprofileform.h000066400000000000000000000046101277565361200202350ustar00rootroot00000000000000#ifndef USERPROFILEFORM_H #define USERPROFILEFORM_H #include #include #include #include "user.h" #include "ui_userprofileform.h" class UserProfileForm : public QDialog, public Ui::UserProfileForm { Q_OBJECT public: UserProfileForm(QWidget* parent = 0); ~UserProfileForm(); virtual t_audio_codec label2codec( const QString & label ); virtual QString codec2label( t_audio_codec & codec ); virtual int ext_support2indexComboItem( t_ext_support ext ); virtual t_ext_support indexComboItem2ext_support( int index ); virtual int exec( list profiles, QString show_profile ); virtual bool check_dynamic_payload( QSpinBox * spb, QList & checked_list ); virtual list get_number_conversions(); virtual bool validateValues(); public slots: virtual void showCategory( int index ); virtual void populate(); virtual void initProfileList( list profiles, QString show_profile_name ); virtual void show( list profiles, QString show_profile ); virtual void validate(); virtual void changeProfile( const QString & profileName ); virtual void chooseFile( QLineEdit * qle, const QString & filter, const QString & caption ); virtual void chooseRingtone(); virtual void chooseRingback(); virtual void chooseIncomingCallScript(); virtual void chooseInCallAnsweredScript(); virtual void chooseInCallFailedScript(); virtual void chooseOutgoingCallScript(); virtual void chooseOutCallAnsweredScript(); virtual void chooseOutCallFailedScript(); virtual void chooseLocalReleaseScript(); virtual void chooseRemoteReleaseScript(); virtual void addCodec(); virtual void removeCodec(); virtual void upCodec(); virtual void downCodec(); virtual void upConversion(); virtual void downConversion(); virtual void addConversion(); virtual void editConversion(); virtual void removeConversion(); virtual void testConversion(); virtual void changeMWIType( int idxMWIType ); virtual void changeSipTransportProtocol( int idx ); signals: void stunServerChanged(t_user *); void authCredentialsChanged(t_user *, const string &); void sipUserChanged(t_user *); void success(); void mwiChangeUnsubscribe(t_user *); void mwiChangeSubscribe(t_user *); protected slots: virtual void languageChange(); private: map map_last_cat; t_user *current_profile; int current_profile_idx; list profile_list; void init(); }; #endif twinkle-1.10.1/src/gui/userprofileform.ui000066400000000000000000006140011277565361200204240ustar00rootroot00000000000000 UserProfileForm 0 0 777 592 Twinkle - User Profile User profile: false 0 0 Select which profile you want to edit. 0 0 150 0 Select a category for which you want to see or modify the settings. 0 32 32 User :/icons/images/penguin.png:/icons/images/penguin.png SIP server :/icons/images/package_network.png:/icons/images/package_network.png Voice mail :/icons/images/mwi_none.png:/icons/images/mwi_none.png Instant message :/icons/images/message32.png:/icons/images/message32.png Presence :/icons/images/presence.png:/icons/images/presence.png RTP audio :/icons/images/kmix.png:/icons/images/kmix.png SIP protocol :/icons/images/package_system.png:/icons/images/package_system.png Transport/NAT :/icons/images/yast_babelfish.png:/icons/images/yast_babelfish.png Address format :/icons/images/yast_PhoneTTOffhook.png:/icons/images/yast_PhoneTTOffhook.png Timers :/icons/images/clock.png:/icons/images/clock.png Ring tones :/icons/images/knotify.png:/icons/images/knotify.png Scripts :/icons/images/edit.png:/icons/images/edit.png Security :/icons/images/encrypted32.png:/icons/images/encrypted32.png Qt::Horizontal QSizePolicy::Expanding 441 20 Accept and save your changes. &OK Alt+O true Undo all your changes and close the window. &Cancel Alt+C 0 0 QFrame::StyledPanel 0 21 QFrame::StyledPanel User false 10 SIP account &User name*: false usernameLineEdit Do&main*: false domainLineEdit Organi&zation: false organizationLineEdit The SIP user name given to you by your provider. It is the user part in your SIP address, <b>username</b>@domain.com This could be a telephone number. <br><br> This field is mandatory. The domain part of your SIP address, username@<b>domain.com</b>. Instead of a real domain this could also be the hostname or IP address of your <b>SIP proxy</b>. If you want direct IP phone to IP phone communications then you fill in the hostname or IP address of your computer. <br><br> This field is mandatory. You may fill in the name of your organization. When you make a call, this might be shown to the called party. This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. &Your name: false displayLineEdit SIP authentication &Realm: false authRealmLineEdit Authentication &name: false authNameLineEdit The realm for authentication. This value must be provided by your SIP provider. If you leave this field empty, then Twinkle will try the user name and password for any realm that it will be challenged with. Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. AKA AM&F: false authAkaAmfLineEdit A&KA OP: false authAkaOpLineEdit Your password for authentication. QLineEdit::Password &Password: false authPasswordLineEdit Authentication management field for AKAv1-MD5 authentication. Operator variant key for AKAv1-MD5 authentication. Qt::Vertical QSizePolicy::Expanding 20 110 21 QFrame::StyledPanel SIP server false 10 Registrar &Registrar: false registrarLineEdit The hostname, domain name or IP address of your registrar. If you use an outbound proxy that is the same as your registrar, then you may leave this field empty and only fill in the address of the outbound proxy. E&xpiry: false expirySpinBox 90 0 The registration expiry time that Twinkle will request. 999999 100 seconds false Qt::Horizontal QSizePolicy::Expanding 260 20 Indicates if Twinkle should automatically register when you run this user profile. You should disable this when you want to do direct IP phone to IP phone communication without a SIP proxy. Re&gister at startup Alt+G The q-value indicates the priority of your registered device. If besides Twinkle you register other SIP devices for this account, then the network may use these values to determine which device to try first when delivering a call. Add q-value to registration The q-value is a value between 0.000 and 1.000. A higher value means a higher priority. Qt::Horizontal QSizePolicy::Expanding 210 20 Outbound Proxy Indicates if Twinkle should use an outbound proxy. If an outbound proxy is used then all SIP requests are sent to this proxy. Without an outbound proxy, Twinkle will try to resolve the SIP address that you type for a call invitation for example to an IP address and send the SIP request there. &Use outbound proxy Alt+U true Outbound &proxy: false proxyLineEdit When you tick this option Twinkle will first try to resolve a SIP address to an IP address itself. If it can, then the SIP request will be sent there. Only when it cannot resolve the address, it will send the SIP request to the proxy (note that an in-dialog request will only be sent to the proxy in this case when you also ticked the previous option.) &Don't send a request to proxy if its destination can be resolved locally. Alt+D true The hostname, domain name or IP address of your outbound proxy. SIP requests within a SIP dialog are normally sent to the address in the contact-headers exchanged during call setup. If you tick this box, that address is ignored and in-dialog request are also sent to the outbound proxy. &Send in-dialog requests to proxy Alt+S Qt::Vertical QSizePolicy::Expanding 20 100 21 QFrame::StyledPanel RTP audio false 10 Co&decs &G.711/G.726 payload size: false ptimeSpinBox 0 0 46 0 32767 32767 The preferred payload size for the G.711 and G.726 codecs. 10 50 10 ms false Qt::Horizontal QSizePolicy::Expanding 121 20 <p> For incoming calls, follow the preference from the far-end (SDP offer). Pick the first codec from the SDP offer that is also in the list of active codecs. <p> If you disable this option, then the first codec from the active codecs that is also in the SDP offer is picked. &Follow codec preference from far end on incoming calls Alt+F <p> For outgoing calls, follow the preference from the far-end (SDP answer). Pick the first codec from the SDP answer that is also in the list of active codecs. <p> If you disable this option, then the first codec from the active codecs that is also in the SDP answer is picked. Follow codec &preference from far end on outgoing calls Alt+P Qt::Vertical QSizePolicy::Expanding 20 16 Codecs Available codecs: false List of available codecs. G.711 A-law G.711 u-law GSM speex-nb (8 kHz) speex-wb (16 kHz) speex-uwb (32 kHz) Qt::Vertical QSizePolicy::Expanding 20 20 Move a codec from the list of available codecs to the list of active codecs. :/icons/images/1rightarrow.png:/icons/images/1rightarrow.png Move a codec from the list of active codecs to the list of available codecs. :/icons/images/1leftarrow.png:/icons/images/1leftarrow.png Qt::Vertical QSizePolicy::Expanding 20 21 Active codecs: false List of active codecs. These are the codecs that will be used for media negotiation during call setup. The order of the codecs is the order of preference of use. Qt::Vertical QSizePolicy::Expanding 20 21 Move a codec upwards in the list of active codecs, i.e. increase its preference of use. :/icons/images/1uparrow.png:/icons/images/1uparrow.png Move a codec downwards in the list of active codecs, i.e. decrease its preference of use. :/icons/images/1downarrow.png:/icons/images/1downarrow.png Qt::Vertical QSizePolicy::Expanding 20 31 Prepr&ocessing Preprocessing (improves quality at remote end) Automatic gain control (AGC) is a feature that deals with the fact that the recording volume may vary by a large amount between different setups. The AGC provides a way to adjust a signal to a reference volume. This is useful because it removes the need for manual adjustment of the microphone gain. A secondary advantage is that by setting the microphone gain to a conservative (low) level, it is easier to avoid clipping. &Automatic gain control Alt+A true Automatic gain control &level: false spxDspAgcLevelSpinBox true Automatic gain control level represents percentual value of automatic gain setting of a microphone. Recommended value is about 25%. 1 100 When enabled, voice activity detection detects whether the input signal represents a speech or a silence/background noise. &Voice activity detection Alt+V The noise reduction can be used to reduce the amount of background noise present in the input signal. This provides higher quality speech. &Noise reduction Alt+N In any VoIP communication, if a speech from the remote end is played in the local loudspeaker, then it propagates in the room and is captured by the microphone. If the audio captured from the microphone is sent directly to the remote end, then the remote user hears an echo of his voice. An acoustic echo cancellation is designed to remove the acoustic echo before it is sent to the remote end. It is important to understand that the echo canceller is meant to improve the quality on the remote end. Acoustic &Echo Cancellation Alt+E Qt::Horizontal QSizePolicy::Expanding 31 20 Qt::Vertical QSizePolicy::Expanding 20 121 &iLBC iLBC i&LBC payload type: false ilbcPayloadSpinBox iLBC &payload size (ms): false ilbcPayloadSizeComboBox The dynamic type value (96 or higher) to be used for iLBC. 96 127 The preferred payload size for iLBC. 20 30 Qt::Horizontal QSizePolicy::Expanding 71 20 Qt::Vertical QSizePolicy::Expanding 20 81 &Speex Speex Perceptual enhancement is a part of the decoder which, when turned on, tries to reduce (the perception of) the noise produced by the coding/decoding process. In most cases, perceptual enhancement make the sound further from the original objectively (if you use SNR), but in the end it still sounds better (subjective improvement). Perceptual &enhancement Alt+E &Ultra wide band payload type: false spxUwbPayloadSpinBox &Wide band payload type: false spxWbPayloadSpinBox Variable bit-rate (VBR) allows a codec to change its bit-rate dynamically to adapt to the "difficulty" of the audio being encoded. In the example of Speex, sounds like vowels and high-energy transients require a higher bit-rate to achieve good quality, while fricatives (e.g. s,f sounds) can be coded adequately with less bits. For this reason, VBR can achieve a lower bit-rate for the same quality, or a better quality for a certain bit-rate. Despite its advantages, VBR has two main drawbacks: first, by only specifying quality, there's no guarantee about the final average bit-rate. Second, for some real-time applications like voice over IP (VoIP), what counts is the maximum bit-rate, which must be low enough for the communication channel. Variable &bit-rate Alt+B The dynamic type value (96 or higher) to be used for speex wide band. 96 127 Discontinuous transmission is an addition to VAD/VBR operation, that allows one to stop transmitting completely when the background noise is stationary. Discontinuous &Transmission Alt+T The dynamic type value (96 or higher) to be used for speex wide band. 96 127 The dynamic type value (96 or higher) to be used for speex narrow band. 96 127 &Quality: false spxQualitySpinBox Speex is a lossy codec, which means that it achives compression at the expense of fidelity of the input speech signal. Unlike some other speech codecs, it is possible to control the tradeoff made between quality and bit-rate. The Speex encoding process is controlled most of the time by a quality parameter that ranges from 0 to 10. 0 10 Co&mplexity: false spxComplexitySpinBox With Speex, it is possible to vary the complexity allowed for the encoder. This is done by controlling how the search is performed with an integer ranging from 1 to 10 in a way that's similar to the -1 to -9 options to gzip and bzip2 compression utilities. For normal use, the noise level at complexity 1 is between 1 and 2 dB higher than at complexity 10, but the CPU requirements for complexity 10 is about 5 times higher than for complexity 1. In practice, the best trade-off is between complexity 2 and 4, though higher settings are often useful when encoding non-speech sounds like DTMF tones. 1 10 &Narrow band payload type: false spxNbPayloadSpinBox Qt::Horizontal QSizePolicy::Expanding 31 20 Qt::Vertical QSizePolicy::Expanding 20 121 G.726 G.726 G.726 &40 kbps payload type: false g72640PayloadSpinBox The dynamic type value (96 or higher) to be used for G.726 40 kbps. 96 127 The dynamic type value (96 or higher) to be used for G.726 32 kbps. 0 127 G.726 &24 kbps payload type: false g72624PayloadSpinBox The dynamic type value (96 or higher) to be used for G.726 24 kbps. 96 127 G.726 &32 kbps payload type: false g72632PayloadSpinBox The dynamic type value (96 or higher) to be used for G.726 16 kbps. 96 127 G.726 &16 kbps payload type: false g72616PayloadSpinBox Qt::Horizontal QSizePolicy::Expanding 231 20 Codeword &packing order: false g726PackComboBox There are 2 standards to pack the G.726 codewords into an RTP packet. RFC 3551 is the default packing method. Some SIP devices use ATM AAL2 however. If you experience bad quality using G.726 with RFC 3551 packing, then try ATM AAL2 packing. RFC 3551 ATM AAL2 Qt::Horizontal QSizePolicy::Expanding 141 20 Qt::Vertical QSizePolicy::Expanding 20 150 DT&MF DTMF Qt::Horizontal QSizePolicy::Expanding 280 20 0 0 49 0 32767 32767 The dynamic type value (96 or higher) to be used for DTMF events (RFC 2833). 96 127 ms false DTMF vo&lume: false dtmfVolumeSpinBox The power level of the DTMF tone in dB. -63 0 10 -10 0 0 49 0 32767 32767 The pause after a DTMF tone. 20 100 10 DTMF &duration: false dtmfDurationSpinBox ms false DTMF payload &type: false dtmfPayloadTypeSpinBox DTMF &pause: false dtmfPauseSpinBox dB false 0 0 49 0 32767 32767 Duration of a DTMF tone. 40 500 10 DTMF t&ransport: false dtmfTransportComboBox <h2>RFC 2833</h2> <p>Send DTMF tones as RFC 2833 telephone events.</p> <h2>Inband</h2> <p>Send DTMF inband.</p> <h2>Auto</h2> <p>If the far end of your call supports RFC 2833, then a DTMF tone will be send as RFC 2833 telephone event, otherwise it will be sent inband. </p> <h2>Out-of-band (SIP INFO)</h2> <p> Send DTMF out-of-band via a SIP INFO request. </p> Auto RFC 2833 Inband Out-of-band (SIP INFO) Qt::Horizontal QSizePolicy::Expanding 161 20 Qt::Vertical QSizePolicy::Expanding 20 120 21 QFrame::StyledPanel SIP protocol false 10 General Qt::Vertical QSizePolicy::Expanding 20 16 Protocol options Call Hold &variant: false holdVariantComboBox 0 0 110 0 Indicates if RFC 2543 (set media IP address in SDP to 0.0.0.0) or RFC 3264 (use direction attributes in SDP) is used to put a call on-hold. RFC 2543 RFC 3264 Qt::Horizontal QSizePolicy::Expanding 70 20 A 200 OK response on a REGISTER request must contain a Contact header. Some registrars however, do not include a Contact header or include a wrong Contact header. This option allows for such a deviation from the specs. Allow m&issing Contact header in 200 OK on REGISTER Alt+I According to RFC 3261 the Max-Forwards header is mandatory. But many implementations do not send this header. If you tick this box, Twinkle will reject a SIP request if Max-Forwards is missing. &Max-Forwards header is mandatory Alt+M In a REGISTER message the expiry time for registration can be put in the Contact header or in the Expires header. If you tick this box it will be put in the Contact header, otherwise it goes in the Expires header. Put &registration expiry time in contact header Alt+R Indicates if compact header names should be used for headers that have a compact form. &Use compact header names Alt+U <p>A SIP UAS may send SDP in a 1XX response for early media, e.g. ringing tone. When the call is answered the SIP UAS should send the same SDP in the 200 OK response according to RFC 3261. Once SDP has been received, SDP in subsequent responses should be discarded.</p> <p>By allowing SDP to change during call setup, Twinkle will not discard SDP in subsequent responses and modify the media stream if the SDP is changed. When the SDP in a response is changed, it must have a new version number in the o= line.</p> Allow SDP change during call setup <p> Twinkle creates a unique contact header value by combining the SIP user name and domain: </p> <p> <tt>&nbsp;user_domain@local_ip</tt> </p> <p> This way 2 user profiles, having the same user name but different domain names, have unique contact addresses and hence can be activated simultaneously. </p> <p> Some proxies do not handle a contact header value like this. You can disable this option to get a contact header value like this: </p> <p> <tt>&nbsp;user@local_ip</tt> </p> <p> This format is what most SIP phones use. </p> Use domain &name to create a unique contact header value Alt+N The Via, Route and Record-Route headers can be encoded as a list of comma separated values or as multiple occurrences of the same header. &Encode Via, Route, Record-Route as list Alt+E Redirection Indicates if Twinkle should redirect a request if a 3XX response is received. &Allow redirection Alt+A Indicates if Twinkle should ask the user before redirecting a request when a 3XX response is received. Ask user &permission to redirect Alt+P &Max redirections: false maxRedirectSpinBox 0 0 46 0 The number of redirect addresses that Twinkle tries at a maximum before it gives up redirecting a request. This prevents a request from getting redirected forever. 1 5 Qt::Horizontal QSizePolicy::Expanding 80 20 SIP extensions 0 0 120 0 Indicates if the 100rel extension (PRACK) is supported:<br><br> <b>disabled</b>: 100rel extension is disabled <br><br> <b>supported</b>: 100rel is supported (it is added in the supported header of an outgoing INVITE). A far-end can now require a PRACK on a 1xx response. <br><br> <b>required</b>: 100rel is required (it is put in the require header of an outgoing INVITE). If an incoming INVITE indicates that it supports 100rel, then Twinkle will require a PRACK when sending a 1xx response. A call will fail when the far-end does not support 100rel. <br><br> <b>preferred</b>: Similar to required, but if a call fails because the far-end indicates it does not support 100rel (420 response) then the call will be re-attempted without the 100rel requirement. disabled supported required preferred &100 rel (PRACK): false ext100relComboBox Indicates if the Replaces-extenstion is supported. Replaces REFER Call transfer (REFER) Indicates if Twinkle should transfer a call if a REFER request is received. Accept call &transfer request (incoming REFER) Alt+T Indicates if Twinkle should ask the user before transferring a call when a REFER request is received. As&k user permission to transfer Alt+K Indicates if Twinkle should put the current call on hold when a REFER request to transfer a call is received. Hold call &with referrer while setting up call to transfer target Alt+W Indicates if Twinkle should put the current call on hold when you transfer a call. Ho&ld call with referee before sending REFER Alt+L While a call is being transferred, the referee sends NOTIFY messages to the referrer about the progress of the transfer. These messages are only sent for a short interval which length is determined by the referee. If you tick this box, the referrer will automatically send a SUBSCRIBE to lengthen this interval if it is about to expire and the transfer has not yet been completed. Auto re&fresh subscription to refer event while call transfer is not finished Alt+F An attended call transfer should use the contact URI as a refer target. A contact URI may not be globally routable however. Alternatively the AoR (Address of Record) may be used. A disadvantage is that the AoR may route to multiple endpoints in case of forking whereas the contact URI routes to a single endoint. Attended refer to AoR (Address of Record) When you perform an attended call transfer, you normally transfer the call after you established a consultation call. If you enable this option you can transfer the call while the consultation call is still in progress. This is a non-standard implementation and may not work with all SIP devices. Allow call transfer while consultation in progress Qt::Vertical QSizePolicy::Expanding 20 200 Privacy Privacy options Include a P-Preferred-Identity header with your identity in an INVITE request for a call with identity hiding. &Send P-Preferred-Identity header when hiding user identity Alt+S Qt::Vertical QSizePolicy::Expanding 20 331 21 QFrame::StyledPanel Transport/NAT false 10 SIP transport Transport mode for SIP. In auto mode, the size of a message determines which transport protocol is used. Messages larger than the UDP threshold are sent via TCP. Smaller messages are sent via UDP. Auto UDP TCP Qt::Horizontal QSizePolicy::Expanding 151 20 T&ransport protocol: false sipTransportComboBox UDP t&hreshold: false udpThresholdSpinBox Messages larger than the threshold are sent via TCP. Smaller messages are sent via UDP. bytes 65535 100 1300 Qt::Horizontal QSizePolicy::Expanding 81 20 NAT traversal Choose this option when there is no NAT device between you and your SIP proxy or when your SIP provider offers hosted NAT traversal. &NAT traversal not needed Alt+N Indicates if Twinkle should use the public IP address specified in the next field inside SIP message, i.e. in SIP headers and SDP body instead of the IP address of your network interface.<br><br> When you choose this option you have to create static address mappings in your NAT device as well. You have to map the RTP ports on the public IP address to the same ports on the private IP address of your PC. &Use statically configured public IP address inside SIP messages Alt+U &Public IP address: false 21 publicIPLineEdit The public IP address of your NAT. Choose this option when your SIP provider offers a STUN server for NAT traversal. Use STUN (does not wor&k for incoming TCP) Alt+S STUN ser&ver: false 21 stunServerLineEdit The hostname, domain name or IP address of the STUN server. Keep the TCP connection established during registration open such that the SIP proxy can reuse this connection to send incoming requests. Application ping packets are sent to test if the connection is still alive. P&ersistent TCP connection Alt+E Send UDP NAT keep alive packets. Enable NAT &keep alive Alt+K Qt::Vertical QSizePolicy::Expanding 20 80 21 QFrame::StyledPanel Address format false 10 Telephone numbers If a URI indicates a telephone number, then only display the user part. E.g. if a call comes in from sip:123456@twinklephone.com then display only "123456" to the user. A URI indicates a telephone number if it contains the "user=phone" parameter or when it has a numerical user part and you ticked the next option. Only &display user part of URI for telephone number Alt+D If you tick this option, then Twinkle considers a SIP address that has a user part that consists of digits, *, #, + and special symbols only as a telephone number. In an outgoing message, Twinkle will add the "user=phone" parameter to such a URI. &URI with numerical user part is a telephone number Alt+U Telephone numbers are often written with special symbols like dashes and brackets to make them readable to humans. When you dial such a number the special symbols must not be dialed. To allow you to simply copy/paste such a number into Twinkle, Twinkle can remove these symbols when you hit the dial button. &Remove special symbols from numerical dial strings Alt+R Expand a dialed telephone number to a tel-URI instead of a sip-URI. Use tel-URI for telephone &number Alt+N &Special symbols: false specialLineEdit The special symbols that may be part of a telephone number for nice formatting, but must be removed when dialing. Number conversion <p> Often the format of the telphone numbers you need to dial is different from the format of the telephone numbers stored in your address book, e.g. your numbers start with a +-symbol followed by a country code, but your provider expects '00' instead of the '+', or you are at the office and all your numbers need to be prefixed with a '9' to access an outside line. Here you can specify number format conversion using Perl style regular expressions and format strings. </p> <p> For each number you dial, Twinkle will try to find a match in the list of match expressions. For the first match it finds, the number will be replaced with the format string. If no match is found, the number stays unchanged. </p> <p> The number conversion rules are also applied to incoming calls, so the numbers are displayed in the format you want. </p> <h3>Example 1</h3> <p> Assume your country code is 31 and you have stored all numbers in your address book in full international number format, e.g. +318712345678. For dialling numbers in your own country you want to strip of the '+31' and replace it by a '0'. For dialling numbers abroad you just want to replace the '+' by '00'. </p> <p> The following rules will do the trick: </p> <blockquote> <tt> Match expression = \+31([0-9]*) , Replace = 0$1<br> Match expression = \+([0-9]*) , Replace = 00$1</br> </tt> </blockquote> <h3>Example 2</h3> <p> You are at work and all telephone numbers starting with a 0 should be prefixed with a 9 for an outside line. </p> <blockquote> <tt> Match expression = 0[0-9]* , Replace = 9$&<br> </tt> </blockquote> QAbstractItemView::SingleSelection QAbstractItemView::SelectRows true false false Match expression Replace Qt::Vertical QSizePolicy::Expanding 20 21 Move the selected number conversion rule upwards in the list. :/icons/images/1uparrow.png:/icons/images/1uparrow.png Move the selected number conversion rule downwards in the list. :/icons/images/1downarrow.png:/icons/images/1downarrow.png Qt::Vertical QSizePolicy::Expanding 20 31 Add a number conversion rule. &Add Alt+A Remove the selected number conversion rule. Re&move Alt+M Edit the selected number conversion rule. &Edit Alt+E Qt::Horizontal QSizePolicy::Expanding 291 20 Type a telephone number here an press the Test button to see how it is converted by the list of number conversion rules. Test how a number is converted by the number conversion rules. &Test Alt+T Qt::Vertical QSizePolicy::Expanding 20 20 21 QFrame::StyledPanel Timers false 10 seconds false 0 0 55 0 55 32767 If you have enabled STUN or NAT keep alive, then Twinkle will send keep alive packets at this interval rate to keep the address bindings in your NAT device alive. 10 900 10 0 0 55 0 55 32767 When an incoming call is received, this timer is started. If the user answers the call, the timer is stopped. If the timer expires before the user answers the call, then Twinkle will reject the call with a "480 User Not Responding". 600 10 NAT &keep alive: false tmrNatKeepaliveSpinBox &No answer: false tmrNoanswerSpinBox Qt::Horizontal QSizePolicy::Expanding 270 20 Qt::Vertical QSizePolicy::Expanding 20 450 21 QFrame::StyledPanel Ring tones false 10 Qt::TabFocus Select ring back tone file. :/icons/images/fileopen.png:/icons/images/fileopen.png Qt::TabFocus Select ring tone file. :/icons/images/fileopen.png:/icons/images/fileopen.png Ring &back tone: false ringbackLineEdit <p> Specify the file name of a .wav file that you want to be played as ring back tone for this user. </p> <p> This ring back tone overrides the ring back tone settings in the system settings. </p> <p> Specify the file name of a .wav file that you want to be played as ring tone for this user. </p> <p> This ring tone overrides the ring tone settings in the system settings. </p> &Ring tone: false ringtoneLineEdit Qt::Vertical QSizePolicy::Expanding 20 391 21 QFrame::StyledPanel Scripts false 10 <p> This script is called when you release a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing SIP BYE request are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=local_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the BYE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. Qt::TabFocus Select script file. :/icons/images/fileopen.png:/icons/images/fileopen.png Qt::TabFocus Select script file. :/icons/images/fileopen.png:/icons/images/fileopen.png Qt::TabFocus Select script file. :/icons/images/fileopen.png:/icons/images/fileopen.png <p> This script is called when an incoming call fails. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing SIP failure response are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=in_call_failed</b>. <b>SIPSTATUS_CODE</b> contains the status code of the failure response. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. Qt::TabFocus Select script file. :/icons/images/fileopen.png:/icons/images/fileopen.png <p> This script is called when the remote party releases a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming SIP BYE request are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=remote_release</b>. <b>SIPREQUEST_METHOD=BYE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the BYE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. Qt::TabFocus Select script file. :/icons/images/fileopen.png:/icons/images/fileopen.png <p> You can customize the way Twinkle handles incoming calls. Twinkle can call a script when a call comes in. Based on the output of the script Twinkle accepts, rejects or redirects the call. When accepting the call, the ring tone can be customized by the script as well. The script can be any executable program. </p> <p> <b>Note:</b> Twinkle pauses while your script runs. It is recommended that your script does not take more than 200 ms. When you need more time, you can send the parameters followed by <b>end</b> and keep on running. Twinkle will continue when it receives the <b>end</b> parameter. </p> <p> With your script you can customize call handling by outputting one or more of the following parameters to stdout. Each parameter should be on a separate line. </p> <p> <blockquote> <tt> action=[ continue | reject | dnd | redirect | autoanswer ]<br> reason=&lt;string&gt;<br> contact=&lt;address to redirect to&gt;<br> caller_name=&lt;name of caller to display&gt;<br> ringtone=&lt;file name of .wav file&gt;<br> display_msg=&lt;message to show on display&gt;<br> end<br> </tt> </blockquote> </p> <h2>Parameters</h2> <h3>action</h3> <p> <b>continue</b> - continue call handling as usual<br> <b>reject</b> - reject call<br> <b>dnd</b> - deny call with do not disturb indication<br> <b>redirect</b> - redirect call to address specified by <b>contact</b><br> <b>autoanswer</b> - automatically answer a call<br> </p> <p> When the script does not write an action to stdout, then the default action is continue. </p> <p> <b>reason: </b> With the reason parameter you can set the reason string for reject or dnd. This might be shown to the far-end user. </p> <p> <b>caller_name: </b> This parameter will override the display name of the caller. </p> <p> <b>ringtone: </b> The ringtone parameter specifies the .wav file that will be played as ring tone when action is continue. </p> <h2>Environment variables</h2> <p> The values of all SIP headers in the incoming INVITE message are passed in environment variables to your script. The variable names are formatted as <b>SIP_&lt;HEADER_NAME&gt;</b> E.g. SIP_FROM contains the value of the from header. </p> <p> TWINKLE_TRIGGER=in_call. SIPREQUEST_METHOD=INVITE. The request-URI of the INVITE will be passed in <b>SIPREQUEST_URI</b>. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> This script is called when the remote party answers your call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming 200 OK are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> This script is called when you answer an incoming call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing 200 OK are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=in_call_answered</b>. <b>SIPSTATUS_CODE=200</b>. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. Call released locall&y: false inCallFailedLineEdit Qt::TabFocus Select script file. :/icons/images/fileopen.png:/icons/images/fileopen.png <p> This script is called when an outgoing call fails. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the incoming SIP failure response are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call_failed</b>. <b>SIPSTATUS_CODE</b> contains the status code of the failure response. <b>SIPSTATUS_REASON</b> contains the reason phrase. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. <p> This script is called when you make a call. </p> <h2>Environment variables</h2> <p> The values of all SIP headers of the outgoing INVITE are passed in environment variables to your script. </p> <p> <b>TWINKLE_TRIGGER=out_call</b>. <b>SIPREQUEST_METHOD=INVITE</b>. <b>SIPREQUEST_URI</b> contains the request-URI of the INVITE. The name of the user profile will be passed in <b>TWINKLE_USER_PROFILE</b>. Outgoing call a&nswered: false inCallAnsweredLineEdit Incoming call &failed: false inCallFailedLineEdit &Incoming call: false incomingCallScriptLineEdit Qt::TabFocus Select script file. :/icons/images/fileopen.png:/icons/images/fileopen.png Call released &remotely: false inCallFailedLineEdit Incoming call &answered: false inCallAnsweredLineEdit Qt::TabFocus Select script file. :/icons/images/fileopen.png:/icons/images/fileopen.png O&utgoing call: false incomingCallScriptLineEdit Out&going call failed: false inCallFailedLineEdit Qt::Vertical QSizePolicy::Expanding 20 190 21 QFrame::StyledPanel Security false 10 When ZRTP/SRTP is enabled, then Twinkle will try to encrypt the audio of each call you originate or receive. Encryption will only succeed if the remote party has ZRTP/SRTP support enabled. If the remote party does not support ZRTP/SRTP, then the audio channel will stay unecrypted. &Enable ZRTP/SRTP encryption Alt+E ZRTP settings A SIP endpoint supporting ZRTP may indicate ZRTP support during call setup in its signalling. Enabling this option will cause Twinkle only to encrypt calls when the remote party indicates ZRTP support. O&nly encrypt audio if remote party indicated ZRTP support in SDP Alt+N Twinkle will indicate ZRTP support during call setup in its signalling. &Indicate ZRTP support in SDP Alt+I A remote party of an encrypted call may send a ZRTP go-clear command to stop encryption. When Twinkle receives this command it will popup a warning if this option is enabled. &Popup warning when remote party disables encryption during call Alt+P Qt::Vertical QSizePolicy::Expanding 20 241 21 QFrame::StyledPanel Voice mail false 10 &Voice mail address: false vmAddressLineEdit The SIP address or telephone number to access your voice mail. <H2>Message waiting indication type</H2> <p> If your provider offers the message waiting indication service, then Twinkle can show you when new voice mail messages are waiting. Ask your provider which type of message waiting indication is offered. </p> <H3>Unsollicited</H3> <p> Asterisk provides unsollicited message waiting indication. </p> <H3>Sollicited</H3> <p> Sollicited message waiting indication as specified by RFC 3842. </p> Unsollicited Sollicited Qt::Horizontal QSizePolicy::Expanding 221 20 &MWI type: false mwiTypeComboBox Sollicited MWI Qt::Horizontal QSizePolicy::Expanding 120 20 Subscription &duration: false mwiDurationSpinBox Mailbox &user name: false mwiUserLineEdit The hostname, domain name or IP address of your voice mailbox server. 90 0 For sollicited MWI, an endpoint subscribes to the message status for a limited duration. Just before the duration expires, the endpoint should refresh the subscription. 999999 100 seconds false Qt::Horizontal QSizePolicy::Expanding 190 20 Your user name for accessing your voice mailbox. Mailbox &server: false mwiServerLineEdit Check this option if Twinkle should send SIP messages to the mailbox server via the outbound proxy. Via outbound &proxy Alt+P Qt::Vertical QSizePolicy::Expanding 20 211 21 QFrame::StyledPanel Instant message false 10 &Maximum number of sessions: false imMaxSessionsSpinBox When you have this number of instant message sessions open, new incoming message sessions will be rejected. 65535 Qt::Horizontal QSizePolicy::Expanding 201 20 Twinkle sends a composing indication when you type a message. This way the recipient can see that you are typing. &Send composing indications when typing a message. Alt+S Qt::Vertical QSizePolicy::Expanding 20 350 21 QFrame::StyledPanel Presence false 10 Your presence Publish your availability at startup. &Publish availability at startup Alt+P Publication &refresh interval (sec): false presPublishTimeSpinBox Refresh rate of presence publications. 999999 100 Qt::Horizontal QSizePolicy::Expanding 231 20 Buddy presence &Subscription refresh interval (sec): false presSubscribeTimeSpinBox Refresh rate of presence subscriptions. 999999 100 Qt::Horizontal QSizePolicy::Expanding 191 20 Qt::Vertical QSizePolicy::Expanding 20 281 displayLineEdit usernameLineEdit domainLineEdit organizationLineEdit authRealmLineEdit authNameLineEdit authPasswordLineEdit authAkaOpLineEdit authAkaAmfLineEdit registrarLineEdit expirySpinBox regAtStartupCheckBox regAddQvalueCheckBox regQvalueLineEdit useProxyCheckBox proxyLineEdit allRequestsCheckBox proxyNonResolvableCheckBox vmAddressLineEdit mwiTypeComboBox mwiUserLineEdit mwiServerLineEdit mwiViaProxyCheckBox mwiDurationSpinBox imMaxSessionsSpinBox isComposingCheckBox presPublishCheckBox presPublishTimeSpinBox presSubscribeTimeSpinBox rtpAudioTabWidget availCodecListBox addCodecPushButton rmvCodecPushButton activeCodecListBox upCodecPushButton downCodecPushButton ptimeSpinBox inFarEndCodecPrefCheckBox outFarEndCodecPrefCheckBox spxDspAgcCheckBox spxDspAgcLevelSpinBox spxDspVadCheckBox spxDspNrdCheckBox spxDspAecCheckBox ilbcPayloadSpinBox ilbcPayloadSizeComboBox spxVbrCheckBox spxDtxCheckBox spxPenhCheckBox spxQualitySpinBox spxComplexitySpinBox spxNbPayloadSpinBox spxWbPayloadSpinBox spxUwbPayloadSpinBox g72616PayloadSpinBox g72624PayloadSpinBox g72632PayloadSpinBox g72640PayloadSpinBox g726PackComboBox dtmfTransportComboBox dtmfPayloadTypeSpinBox dtmfDurationSpinBox dtmfPauseSpinBox dtmfVolumeSpinBox sipProtoclTabWidget holdVariantComboBox maxForwardsCheckBox missingContactCheckBox regTimeCheckBox compactHeadersCheckBox multiValuesListCheckBox useDomainInContactCheckBox allowSdpChangeCheckBox allowRedirectionCheckBox askUserRedirectCheckBox maxRedirectSpinBox ext100relComboBox extReplacesCheckBox allowReferCheckBox askUserReferCheckBox refereeHoldCheckBox referrerHoldCheckBox refreshReferSubCheckBox referAorCheckBox pPreferredIdCheckBox sipTransportComboBox udpThresholdSpinBox displayTelUserCheckBox numericalUserIsTelCheckBox removeSpecialCheckBox specialLineEdit useTelUriCheckBox conversionListView upConversionPushButton downConversionPushButton addConversionPushButton removePushButton editConversionPushButton testConversionLineEdit testConversionPushButton tmrNoanswerSpinBox tmrNatKeepaliveSpinBox ringtoneLineEdit ringbackLineEdit openRingtoneToolButton openRingbackToolButton incomingCallScriptLineEdit openIncomingCallScriptToolButton inCallAnsweredLineEdit openInCallAnsweredToolButton inCallFailedLineEdit openInCallFailedToolButton outCallLineEdit openOutCallToolButton outCallAnsweredLineEdit openOutCallAnsweredToolButton outCallFailedLineEdit openOutCallFailedToolButton localReleaseLineEdit openLocalReleaseToolButton remoteReleaseLineEdit openRemoteReleaseToolButton zrtpEnabledCheckBox zrtpSendIfSupportedCheckBox zrtpSdpCheckBox zrtpGoClearWarningCheckBox okPushButton cancelPushButton profileComboBox categoryListBox user.h map list categoryListBox currentRowChanged(int) UserProfileForm showCategory(int) 31 63 20 20 cancelPushButton clicked() UserProfileForm reject() 126 576 20 20 okPushButton clicked() UserProfileForm validate() 39 576 20 20 useProxyCheckBox toggled(bool) proxyTextLabel setEnabled(bool) 240 329 240 357 useProxyCheckBox toggled(bool) proxyLineEdit setEnabled(bool) 240 329 353 357 useProxyCheckBox toggled(bool) allRequestsCheckBox setEnabled(bool) 240 329 240 387 allowRedirectionCheckBox toggled(bool) askUserRedirectCheckBox setEnabled(bool) 242 456 242 484 allowRedirectionCheckBox toggled(bool) maxRedirectTextLabel setEnabled(bool) 242 456 242 512 allowRedirectionCheckBox toggled(bool) maxRedirectSpinBox setEnabled(bool) 242 456 358 512 useProxyCheckBox toggled(bool) proxyNonResolvableCheckBox setEnabled(bool) 240 329 240 415 allowReferCheckBox toggled(bool) askUserReferCheckBox setEnabled(bool) 242 178 242 174 allowReferCheckBox toggled(bool) refereeHoldCheckBox setEnabled(bool) 242 178 242 170 profileComboBox activated(QString) UserProfileForm changeProfile(QString) 116 32 20 20 openRingtoneToolButton clicked() UserProfileForm chooseRingtone() 281 60 20 20 openRingbackToolButton clicked() UserProfileForm chooseRingback() 281 64 20 20 openIncomingCallScriptToolButton clicked() UserProfileForm chooseIncomingCallScript() 281 60 20 20 addCodecPushButton clicked() UserProfileForm addCodec() 451 266 20 20 rmvCodecPushButton clicked() UserProfileForm removeCodec() 451 300 20 20 upCodecPushButton clicked() UserProfileForm upCodec() 702 266 20 20 downCodecPushButton clicked() UserProfileForm downCodec() 702 300 20 20 availCodecListBox itemDoubleClicked(QListWidgetItem*) UserProfileForm addCodec() 243 211 20 20 activeCodecListBox itemDoubleClicked(QListWidgetItem*) UserProfileForm removeCodec() 493 211 20 20 openInCallAnsweredToolButton clicked() UserProfileForm chooseInCallAnsweredScript() 281 62 20 20 openInCallFailedToolButton clicked() UserProfileForm chooseInCallFailedScript() 281 63 20 20 openLocalReleaseToolButton clicked() UserProfileForm chooseLocalReleaseScript() 281 68 20 20 openOutCallAnsweredToolButton clicked() UserProfileForm chooseOutCallAnsweredScript() 281 66 20 20 openOutCallFailedToolButton clicked() UserProfileForm chooseOutCallFailedScript() 281 67 20 20 openOutCallToolButton clicked() UserProfileForm chooseOutgoingCallScript() 281 64 20 20 openRemoteReleaseToolButton clicked() UserProfileForm chooseRemoteReleaseScript() 281 70 20 20 upConversionPushButton clicked() UserProfileForm upConversion() 266 101 20 20 downConversionPushButton clicked() UserProfileForm downConversion() 266 103 20 20 addConversionPushButton clicked() UserProfileForm addConversion() 228 89 20 20 editConversionPushButton clicked() UserProfileForm editConversion() 260 89 20 20 removePushButton clicked() UserProfileForm removeConversion() 244 89 20 20 testConversionPushButton clicked() UserProfileForm testConversion() 267 80 20 20 zrtpEnabledCheckBox toggled(bool) zrtpSettingsGroupBox setEnabled(bool) 225 58 225 62 mwiTypeComboBox activated(int) UserProfileForm changeMWIType(int) 269 64 20 20 regAddQvalueCheckBox toggled(bool) regQvalueLineEdit setEnabled(bool) 241 251 447 250 sipTransportComboBox activated(int) UserProfileForm changeSipTransportProtocol(int) 368 158 20 20 spxDspAgcCheckBox toggled(bool) spxDspAgcLevelTextLabel setEnabled(bool) 242 179 258 179 spxDspAgcCheckBox toggled(bool) spxDspAgcLevelSpinBox setEnabled(bool) 242 179 274 179 natStunRadioButton toggled(bool) stunServerLineEdit setEnabled(bool) 418 351 431 374 natStaticRadioButton toggled(bool) publicIPLineEdit setEnabled(bool) 334 285 437 312 twinkle-1.10.1/src/gui/wizardform.cpp000066400000000000000000000176131277565361200175400ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include #include #include "gui.h" #include #include "wizardform.h" #define PROV_NONE QT_TRANSLATE_NOOP("WizardForm", "None (direct IP to IP calls)") #define PROV_OTHER QT_TRANSLATE_NOOP("WizardForm", "Other") struct t_provider { QString domain; QString sip_proxy; QString stun_server; }; /* * Constructs a WizardForm as a child of 'parent', with the * name 'name' and widget flags set to 'f'. * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ WizardForm::WizardForm(QWidget* parent) : QDialog(parent) { setupUi(this); init(); } /* * Destroys the object and frees any allocated resources */ WizardForm::~WizardForm() { // no need to delete child widgets, Qt does it all for us } /* * Sets the strings of the subwidgets using the current * language. */ void WizardForm::languageChange() { retranslateUi(this); } void WizardForm::init() { QRegExp rxNoSpace("\\S*"); // Set validators usernameLineEdit->setValidator(new QRegExpValidator(rxNoSpace, this)); domainLineEdit->setValidator(new QRegExpValidator(rxNoSpace, this)); authNameLineEdit->setValidator(new QRegExpValidator(rxNoSpace, this)); proxyLineEdit->setValidator(new QRegExpValidator(rxNoSpace, this)); initProviders(); serviceProviderComboBox->setCurrentIndex(serviceProviderComboBox->count() - 1); update(tr(PROV_OTHER)); } void WizardForm::initProviders() { serviceProviderComboBox->clear(); serviceProviderComboBox->addItem(tr(PROV_NONE)); QString fname = sys_config->get_dir_share().c_str(); fname.append("/").append(FILE_PROVIDERS); QFile providersFile(fname); if (providersFile.open(QIODevice::ReadOnly)) { QTextStream providersStream(&providersFile); QString entry; while ((entry = providersStream.readLine()) != QString::null) { // Skip comment if (entry[0] == '#') continue; QStringList l = entry.split(";", QString::KeepEmptyParts); // Skip invalid lines if (l.size() != 4) continue; t_provider p; p.domain = l[1]; p.sip_proxy = l[2]; p.stun_server = l[3]; mapProviders[l[0]] = p; serviceProviderComboBox->addItem(l[0]); } providersFile.close(); } serviceProviderComboBox->addItem(tr(PROV_OTHER)); } int WizardForm::exec(t_user *user) { user_config = user; // Set user profile name in the titlebar QString s = PRODUCT_NAME; s.append(" - ").append(tr("User profile wizard:")).append(" "); s.append(user_config->get_profile_name().c_str()); setWindowTitle(s); return QDialog::exec(); } void WizardForm::show(t_user *user) { user_config = user; // Set user profile name in the titlebar QString s = PRODUCT_NAME; s.append(" - ").append(tr("User profile wizard:")).append(" "); s.append(user_config->get_profile_name().c_str()); setWindowTitle(s); QDialog::show(); } void WizardForm::update(const QString &item) { // Disable/Enable controls if (item == tr(PROV_NONE)) { suggestAuthName = false; authNameTextLabel->setEnabled(false); authNameLineEdit->setEnabled(false); authPasswordTextLabel->setEnabled(false); authPasswordLineEdit->setEnabled(false); proxyTextLabel->setEnabled(false); proxyLineEdit->setEnabled(false); stunServerTextLabel->setEnabled(false); stunServerLineEdit->setEnabled(false); } else { if (usernameLineEdit->text() == authNameLineEdit->text()) { suggestAuthName = true; } else { suggestAuthName = false; } authNameTextLabel->setEnabled(true); authNameLineEdit->setEnabled(true); authPasswordTextLabel->setEnabled(true); authPasswordLineEdit->setEnabled(true); proxyTextLabel->setEnabled(true); proxyLineEdit->setEnabled(true); stunServerTextLabel->setEnabled(true); stunServerLineEdit->setEnabled(true); } // Set values if (item == tr(PROV_NONE)) { domainLineEdit->clear(); authNameLineEdit->clear(); authPasswordLineEdit->clear(); proxyLineEdit->clear(); stunServerLineEdit->clear(); } else if (item == tr(PROV_OTHER)) { domainLineEdit->clear(); stunServerLineEdit->clear(); proxyLineEdit->clear(); } else { t_provider p = mapProviders[item]; domainLineEdit->setText(p.domain); proxyLineEdit->setText(p.sip_proxy); stunServerLineEdit->setText(p.stun_server); } } void WizardForm::updateAuthName(const QString &s) { if (suggestAuthName) { authNameLineEdit->setText(s); } } void WizardForm::disableSuggestAuthName() { suggestAuthName = false; } void WizardForm::validate() { QString s; // Validity check user page // SIP username is mandatory if (usernameLineEdit->text().isEmpty()) { ((t_gui *)ui)->cb_show_msg(this, tr("You must fill in a user name for your SIP account.").toStdString(), MSG_CRITICAL); usernameLineEdit->setFocus(); return; } // SIP user domain is mandatory if (domainLineEdit->text().isEmpty()) { ((t_gui *)ui)->cb_show_msg(this, tr( "You must fill in a domain name for your SIP account.\n" "This could be the hostname or IP address of your PC " "if you want direct PC to PC dialing.").toStdString(), MSG_CRITICAL); domainLineEdit->setFocus(); return; } // SIP proxy if (proxyLineEdit->text() != "") { s = USER_SCHEME; s.append(':').append(proxyLineEdit->text()); t_url u(s.toStdString()); if (!u.is_valid() || u.get_user() != "") { ((t_gui *)ui)->cb_show_msg(this, tr("Invalid value for SIP proxy.").toStdString(), MSG_CRITICAL); proxyLineEdit->setFocus(); proxyLineEdit->selectAll(); return; } } // Register and publish presence at startup if (serviceProviderComboBox->currentText() == tr(PROV_NONE)) { user_config->set_register_at_startup(false); user_config->set_pres_publish_startup(false); } // STUN server if (stunServerLineEdit->text() != "") { s = "stun:"; s.append(stunServerLineEdit->text()); t_url u(s.toStdString()); if (!u.is_valid() || u.get_user() != "") { ((t_gui *)ui)->cb_show_msg(this, tr("Invalid value for STUN server.").toStdString(), MSG_CRITICAL); stunServerLineEdit->setFocus(); stunServerLineEdit->selectAll(); return; } } // Set all values in the user_config object // USER user_config->set_display(displayLineEdit->text().toStdString()); user_config->set_name(usernameLineEdit->text().toStdString()); user_config->set_domain(domainLineEdit->text().toStdString()); user_config->set_auth_name(authNameLineEdit->text().toStdString()); user_config->set_auth_pass(authPasswordLineEdit->text().toStdString()); // SIP SERVER user_config->set_use_outbound_proxy(!proxyLineEdit->text().isEmpty()); s = USER_SCHEME; s.append(':').append(proxyLineEdit->text()); user_config->set_outbound_proxy(t_url(s.toStdString())); // NAT user_config->set_use_stun(!stunServerLineEdit->text().isEmpty()); s = "stun:"; s.append(stunServerLineEdit->text()); user_config->set_stun_server(t_url(s.toStdString())); // Save user config string error_msg; if (!user_config->write_config(user_config->get_filename(), error_msg)) { // Failed to write config file ((t_gui *)ui)->cb_show_msg(this, error_msg, MSG_CRITICAL); return; } emit success(); accept(); } twinkle-1.10.1/src/gui/wizardform.h000066400000000000000000000013471277565361200172020ustar00rootroot00000000000000#ifndef WIZARDFORM_H #define WIZARDFORM_H struct t_provider; #include #include "user.h" #include "ui_wizardform.h" class WizardForm : public QDialog, public Ui::WizardForm { Q_OBJECT public: WizardForm(QWidget* parent = 0); ~WizardForm(); virtual void show( t_user * user ); public slots: virtual void initProviders(); virtual int exec( t_user * user ); virtual void update( const QString & item ); virtual void updateAuthName( const QString & s ); virtual void disableSuggestAuthName(); virtual void validate(); signals: void success(); protected slots: virtual void languageChange(); private: bool suggestAuthName; std::map mapProviders; t_user *user_config; void init(); }; #endif twinkle-1.10.1/src/gui/wizardform.ui000066400000000000000000000300661277565361200173700ustar00rootroot00000000000000 WizardForm 0 0 596 321 Twinkle - Wizard The hostname, domain name or IP address of the STUN server. S&TUN server: stunServerLineEdit false The SIP user name given to you by your provider. It is the user part in your SIP address, <b>username</b>@domain.com This could be a telephone number. <br><br> This field is mandatory. &Domain*: domainLineEdit false Choose your SIP service provider. If your SIP service provider is not in the list, then select <b>Other</b> and fill in the settings you received from your provider.<br><br> If you select one of the predefined SIP service providers then you only have to fill in your name, user name, authentication name and password. &Authentication name: authNameLineEdit false &Your name: displayLineEdit false Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. 206 20 QSizePolicy::Expanding Qt::Horizontal The domain part of your SIP address, username@<b>domain.com</b>. Instead of a real domain this could also be the hostname or IP address of your <b>SIP proxy</b>. If you want direct IP phone to IP phone communications then you fill in the hostname or IP address of your computer. <br><br> This field is mandatory. This is just your full name, e.g. John Doe. It is used as a display name. When you make a call, this display name might be shown to the called party. true SIP pro&xy: proxyLineEdit false true The hostname, domain name or IP address of your SIP proxy. If this is the same value as your domain, you may leave this field empty. &SIP service provider: serviceProviderComboBox false &Password: authPasswordLineEdit false &User name*: usernameLineEdit false QLineEdit::Password Your password for authentication. 20 20 QSizePolicy::Expanding Qt::Vertical 371 20 QSizePolicy::Expanding Qt::Horizontal &OK Alt+O true &Cancel Alt+C serviceProviderComboBox displayLineEdit usernameLineEdit domainLineEdit authNameLineEdit authPasswordLineEdit proxyLineEdit stunServerLineEdit okPushButton cancelPushButton map user.h okPushButton clicked() WizardForm validate() cancelPushButton clicked() WizardForm reject() usernameLineEdit textChanged(QString) WizardForm updateAuthName(QString) serviceProviderComboBox activated(QString) WizardForm update(QString) authNameLineEdit editingFinished() WizardForm disableSuggestAuthName() twinkle-1.10.1/src/gui/yesnodialog.cpp000066400000000000000000000050221277565361200176600ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "yesnodialog.h" #include "qlabel.h" #include "qlayout.h" //Added by qt3to4: #include #include #include #include "userintf.h" // class YesNoDialog void YesNoDialog::actionYes() { QDialog::accept(); } void YesNoDialog::actionNo() { QDialog::reject(); } YesNoDialog::YesNoDialog() { setAttribute(Qt::WA_DeleteOnClose); } YesNoDialog::YesNoDialog(QWidget *parent, const QString &caption, const QString &text) : QDialog(parent) { setModal(true); setAttribute(Qt::WA_DeleteOnClose); setWindowTitle(caption); QBoxLayout *vb = new QVBoxLayout(this); vb->setMargin(11); vb->setSpacing(6); QLabel *lblQuestion = new QLabel(text, this); vb->addWidget(lblQuestion); QHBoxLayout *hb = new QHBoxLayout(this); hb->setSpacing(6); QSpacerItem *spacer1 = new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum ); hb->addItem(spacer1); pbYes = new QPushButton(tr("&Yes"), this); hb->addWidget(pbYes); pbNo = new QPushButton(tr("&No"), this); hb->addWidget(pbNo); QSpacerItem *spacer2 = new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum ); hb->addItem(spacer2); vb->addLayout(hb); connect(pbYes, SIGNAL(clicked()), this, SLOT(actionYes())); connect(pbNo, SIGNAL(clicked()), this, SLOT(actionNo())); } YesNoDialog::~YesNoDialog() {} void YesNoDialog::reject() { pbNo->animateClick(); } // class ReferPermissionDialog void ReferPermissionDialog::actionYes() { ui->send_refer_permission(true); YesNoDialog::actionYes(); } void ReferPermissionDialog::actionNo() { ui->send_refer_permission(false); YesNoDialog::actionNo(); } ReferPermissionDialog::ReferPermissionDialog(QWidget *parent, const QString &caption, const QString &text) : YesNoDialog(parent, caption, text) {} twinkle-1.10.1/src/gui/yesnodialog.h000066400000000000000000000026031277565361200173270ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _YESNODIALOG_H #define _YESNODIALOG_H #include "qdialog.h" #include "qpushbutton.h" #include "qstring.h" class YesNoDialog : public QDialog { private: Q_OBJECT QPushButton *pbYes; QPushButton *pbNo; protected slots: virtual void actionYes(); virtual void actionNo(); public: YesNoDialog(); YesNoDialog(QWidget *parent, const QString &caption, const QString &text); virtual ~YesNoDialog(); void reject(); }; class ReferPermissionDialog : public YesNoDialog { private: Q_OBJECT protected slots: virtual void actionYes(); virtual void actionNo(); public: ReferPermissionDialog(QWidget *parent, const QString &caption, const QString &text); }; #endif twinkle-1.10.1/src/id_object.cpp000066400000000000000000000022441277565361200165040ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "id_object.h" // Initialization of static members t_mutex t_id_object::mtx_next_id; t_object_id t_id_object::next_id = 1; t_id_object::t_id_object() { mtx_next_id.lock(); id = next_id++; if (next_id == 65535) next_id = 1; mtx_next_id.unlock(); } t_object_id t_id_object::get_object_id() { return id; } void t_id_object::generate_new_id() { mtx_next_id.lock(); id = next_id++; if (next_id == 65535) next_id = 1; mtx_next_id.unlock(); } twinkle-1.10.1/src/id_object.h000066400000000000000000000027251277565361200161550ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Objects with a unique object id. */ #ifndef _ID_OBJECT_H #define _ID_OBJECT_H #include "threads/mutex.h" /** * Object identifier. */ typedef unsigned short t_object_id; /** * Parent class for objects that need a unique object id. */ class t_id_object { private: /** Mutex for concurrent object id creation. */ static t_mutex mtx_next_id; /** Id for the next object. */ static t_object_id next_id; /** Unique object identifier. */ t_object_id id; public: /** Constructor */ t_id_object(); /** * Get the object id. * @return Object id. */ t_object_id get_object_id(); /** * Generate a new object identifier. This can be useful * after making a copy of an object. */ void generate_new_id(); }; #endif twinkle-1.10.1/src/im/000077500000000000000000000000001277565361200144615ustar00rootroot00000000000000twinkle-1.10.1/src/im/CMakeLists.txt000066400000000000000000000002241277565361200172170ustar00rootroot00000000000000project(libtwinkle-im) set(LIBTWINKLE_IM-SRCS im_iscomposing_body.cpp msg_session.cpp ) add_library(libtwinkle-im OBJECT ${LIBTWINKLE_IM-SRCS}) twinkle-1.10.1/src/im/im_iscomposing_body.cpp000066400000000000000000000115211277565361200212210ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "im_iscomposing_body.h" #include #include #include "log.h" #include "util.h" #include "audits/memman.h" #define IM_ISCOMPOSING_NAMESPACE "urn:ietf:params:xml:ns:im-iscomposing" #define IS_IM_ISCOMPOSING_TAG(node, tag) IS_XML_TAG(node, tag, IM_ISCOMPOSING_NAMESPACE) #define IS_IM_ISCOMPOSING_ATTR(attr, attr_name) IS_XML_ATTR(attr, attr_name, IM_ISCOMPOSING_NAMESPACE) bool t_im_iscomposing_xml_body::extract_data(void) { assert(xml_doc); state_.clear(); refresh_ = 0; xmlNode *root_element = NULL; // Get root root_element = xmlDocGetRootElement(xml_doc); if (!root_element) { log_file->write_report("im-iscomposing document has no root element.", "t_im_iscomposing_xml_body::extract_data", LOG_NORMAL, LOG_WARNING); return false; } // Check if root is if (!IS_IM_ISCOMPOSING_TAG(root_element, "isComposing")) { log_file->write_report("im-iscomposing document has invalid root element.", "t_im_iscomposing_xml_body::extract_data", LOG_NORMAL, LOG_WARNING); return false; } xmlNode *child = root_element->children; // Process children of root. bool state_present = false; for (xmlNode *cur_node = child; cur_node; cur_node = cur_node->next) { if (IS_IM_ISCOMPOSING_TAG(cur_node, "state")) { state_present = process_node_state(cur_node); } else if (IS_IM_ISCOMPOSING_TAG(cur_node, "refresh")) { process_node_refresh(cur_node); } } // The state node is mandatory, so return only true if it is present. return state_present; } bool t_im_iscomposing_xml_body::process_node_state(xmlNode *node) { assert(node); xmlNode *child = node->children; if (child && child->type == XML_TEXT_NODE) { state_ = tolower((char*)child->content); } else { log_file->write_report(" element has no content.", "t_im_iscomposing_xml_body::process_node_state", LOG_NORMAL, LOG_WARNING); return false; } return true; } void t_im_iscomposing_xml_body::process_node_refresh(xmlNode *node) { assert(node); xmlNode *child = node->children; if (child && child->type == XML_TEXT_NODE) { refresh_ = atoi((char*)child->content); } else { log_file->write_report(" element has no content.", "t_im_iscomposing_xml_body::process_node_refresh", LOG_NORMAL, LOG_WARNING); } } void t_im_iscomposing_xml_body::create_xml_doc( const string &xml_version, const string &charset) { t_sip_body_xml::create_xml_doc(xml_version, charset); // isComposing xmlNode *node_iscomposing = xmlNewNode(NULL, BAD_CAST "isComposing"); xmlNs *ns_im_iscomposing = xmlNewNs(node_iscomposing, BAD_CAST IM_ISCOMPOSING_NAMESPACE, NULL); xmlDocSetRootElement(xml_doc, node_iscomposing); // state xmlNewChild(node_iscomposing, ns_im_iscomposing, BAD_CAST "state", BAD_CAST state_.c_str()); // refresh if (refresh_ > 0) { xmlNewChild(node_iscomposing, ns_im_iscomposing, BAD_CAST "refresh", BAD_CAST int2str(refresh_).c_str()); } } t_im_iscomposing_xml_body::t_im_iscomposing_xml_body() : t_sip_body_xml (), state_(IM_ISCOMPOSING_STATE_IDLE), refresh_(0) {} t_sip_body *t_im_iscomposing_xml_body::copy(void) const { t_im_iscomposing_xml_body *body = new t_im_iscomposing_xml_body(*this); MEMMAN_NEW(body); // Clear the xml_doc pointer in the new body, as a copy of the // XML document must be copied to the body. body->xml_doc = NULL; copy_xml_doc(body); return body; } t_body_type t_im_iscomposing_xml_body::get_type(void) const { return BODY_IM_ISCOMPOSING_XML; } t_media t_im_iscomposing_xml_body::get_media(void) const { return t_media("application", "im-iscomposing+xml"); } bool t_im_iscomposing_xml_body::parse(const string &s) { if (t_sip_body_xml::parse(s)) { if (!extract_data()) { MEMMAN_DELETE(xml_doc); xmlFreeDoc(xml_doc); xml_doc = NULL; } } return (xml_doc != NULL); } string t_im_iscomposing_xml_body::get_state(void) const { return state_; } time_t t_im_iscomposing_xml_body::get_refresh(void) const { return refresh_; } void t_im_iscomposing_xml_body::set_state(const string &state) { state_ = state; } void t_im_iscomposing_xml_body::set_refresh(time_t refresh) { refresh_ = refresh; } twinkle-1.10.1/src/im/im_iscomposing_body.h000066400000000000000000000043621277565361200206730ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * RFC 3994 im-iscomposing+xml body */ #ifndef _IM_ISCOMPOSING_BODY_H #define _IM_ISCOMPOSING_BODY_H #include #include #include #include "parser/sip_body.h" using namespace std; //@{ /** @name Message composition states */ #define IM_ISCOMPOSING_STATE_IDLE "idle" #define IM_ISCOMPOSING_STATE_ACTIVE "active" //@} /** RFC 3994 im-iscomposing+xml body */ class t_im_iscomposing_xml_body : public t_sip_body_xml { private: string state_; /**< Composition state */ time_t refresh_; /**< Refresh interval in seconds */ /** Extract information elements from the XML document. */ bool extract_data(void); /** * Process the state element. * @param node [in] The state element. */ bool process_node_state(xmlNode *node); /** * Process the refresh element. * @param node [in] The refresh element. */ void process_node_refresh(xmlNode *node); protected: /** * Create a im-iscomposing document from the values stored in the attributes. */ virtual void create_xml_doc(const string &xml_version = "1.0", const string &charset = "UTF-8"); public: /** Constructor */ t_im_iscomposing_xml_body(); virtual t_sip_body *copy(void) const; virtual t_body_type get_type(void) const; virtual t_media get_media(void) const; virtual bool parse(const string &s); /** @name Getters */ //@{ string get_state(void) const; time_t get_refresh(void) const; //@} /** @name Setters */ //@{ void set_state(const string &state); void set_refresh(time_t refresh); //@} }; #endif twinkle-1.10.1/src/im/msg_session.cpp000066400000000000000000000242671277565361200175310ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "msg_session.h" #include #include #include "im_iscomposing_body.h" #include "log.h" #include "phone.h" #include "translator.h" #include "parser/media_type.h" #include "utils/file_utils.h" #define COMPOSING_LOCAL_IDLE_TIMEOUT 15 #define COMPOSING_LOCAL_REFRESH_TIMEOUT 90 extern t_phone *phone; using namespace im; using namespace utils; t_composing_state im::string2composing_state(const string &state_name) { if (state_name == IM_ISCOMPOSING_STATE_ACTIVE) { return COMPOSING_STATE_ACTIVE; } return COMPOSING_STATE_IDLE; } string im::composing_state2string(t_composing_state state) { switch (state) { case COMPOSING_STATE_IDLE: return "idle"; case COMPOSING_STATE_ACTIVE: return "active"; default: assert(false); } return "idle"; } // class t_msg t_msg::t_msg() : has_attachment(false) { struct timeval t; gettimeofday(&t, NULL); timestamp = t.tv_sec; } t_msg::t_msg(const string &msg, t_direction dir, t_text_format fmt) : message(msg), direction(dir), format(fmt), has_attachment(false) { struct timeval t; gettimeofday(&t, NULL); timestamp = t.tv_sec; } void t_msg::set_attachment(const string &filename, const t_media &media, const string &save_as) { attachment_filename = filename; attachment_media = media; attachment_save_as_name = save_as; has_attachment = true; } // class t_msg_session t_msg_session::t_msg_session(t_user *u) : user_config(u), new_message_added(false), error_recvd(false), delivery_notification_recvd(false), msg_in_flight(false), send_composing_state(u->get_im_send_iscomposing()), local_composing_state(COMPOSING_STATE_IDLE), local_idle_timeout(0), local_refresh_timeout(0), remote_composing_state(COMPOSING_STATE_IDLE), remote_idle_timeout(0) {} t_msg_session::t_msg_session(t_user *u, t_display_url _remote_party) : user_config(u), remote_party(_remote_party), new_message_added(false), error_recvd(false), delivery_notification_recvd(false), msg_in_flight(false), send_composing_state(u->get_im_send_iscomposing()), local_composing_state(COMPOSING_STATE_IDLE), local_idle_timeout(0), local_refresh_timeout(0), remote_composing_state(COMPOSING_STATE_IDLE), remote_idle_timeout(0) {} t_msg_session::~t_msg_session() { // Remove temporary files. for (list::iterator it = messages.begin(); it != messages.end(); ++it) { // Temporary files are created for incoming messages only. if (it->has_attachment && it->direction == MSG_DIR_IN) { // Defensive check to make sure we are deleting tmp files only. if (sys_config->is_tmpfile(it->attachment_filename)) { log_file->write_header("t_msg_session::~t_msg_session"); log_file->write_raw("Remove tmp file "); log_file->write_raw(it->attachment_filename); log_file->write_endl(); log_file->write_footer(); unlink(it->attachment_filename.c_str()); } } } } t_user *t_msg_session::get_user(void) const { return user_config; } t_display_url t_msg_session::get_remote_party(void) const { return remote_party; } t_composing_state t_msg_session::get_remote_composing_state(void) const { return remote_composing_state; } void t_msg_session::set_user(t_user *u) { user_config = u; } void t_msg_session::set_remote_party(const t_display_url &du) { remote_party = du; } void t_msg_session::set_send_composing_state(bool enable) { send_composing_state = enable; } t_msg t_msg_session::get_last_message(void) { new_message_added = false; if (messages.empty()) { throw empty_list_exception(); } return messages.back(); } bool t_msg_session::is_new_message_added(void) const { return new_message_added; } void t_msg_session::set_display_if_empty(const string &display) { if (remote_party.display.empty()) { remote_party.display = display; } } const list &t_msg_session::get_messages(void) const { return messages; } void t_msg_session::recv_msg(const t_msg &msg) { // RFC 3994 3.3 // The composing state of the remote party transitions to idle // when a message is received. remote_composing_state = COMPOSING_STATE_IDLE; remote_idle_timeout = 0; messages.push_back(msg); new_message_added = true; notify(); } void t_msg_session::send_msg(const string &message, t_text_format format) { // RFC 3994 3.2 // If a content message is sent before the idle threshold expires, no // "idle" state indication is needed. // The local state is set to idle without sending an indication to the // remote party. local_composing_state = COMPOSING_STATE_IDLE; local_idle_timeout = 0; local_refresh_timeout = 0; t_msg msg(message, im::MSG_DIR_OUT, format); messages.push_back(msg); new_message_added = true; bool ret = phone->pub_send_message(user_config, remote_party.url, remote_party.display, msg); if (ret) { msg_in_flight = true; } else { set_error(TRANSLATE("Failed to send message.")); } notify(); } void t_msg_session::send_file(const string &filename, const t_media &media, const string &subject) { // RFC 3994 3.2 // If a content message is sent before the idle threshold expires, no // "idle" state indication is needed. // The local state is set to idle without sending an indication to the // remote party. local_composing_state = COMPOSING_STATE_IDLE; local_idle_timeout = 0; local_refresh_timeout = 0; t_msg msg; msg.set_attachment(filename, media, strip_path_from_filename(filename)); msg.subject = subject; msg.direction = MSG_DIR_OUT; messages.push_back(msg); new_message_added = true; bool ret = phone->pub_send_message(user_config, remote_party.url, remote_party.display, msg); if (ret) { msg_in_flight = true; notify(); } else { // Notify user interface about the sent message before // setting the error. notify(); set_error(TRANSLATE("Failed to send message.")); } } void t_msg_session::set_error(const string &message) { error_msg = message; error_recvd = true; notify(); } bool t_msg_session::error_received(void) const { return error_recvd; } string t_msg_session::take_error(void) { if (!error_recvd) return ""; error_recvd = false; return error_msg; } void t_msg_session::set_delivery_notification(const string ¬ification) { delivery_notification = notification; delivery_notification_recvd = true; notify(); } bool t_msg_session::delivery_notification_received(void) const { return delivery_notification_recvd; } string t_msg_session::take_delivery_notification(void) { if (!delivery_notification_recvd) return ""; delivery_notification_recvd = false; return delivery_notification; } bool t_msg_session::match(t_user *user, t_url _remote_party) { return user == user_config && _remote_party == remote_party.url; } void t_msg_session::set_msg_in_flight(bool in_flight) { msg_in_flight = in_flight; notify(); } bool t_msg_session::is_msg_in_flight(void) const { return msg_in_flight; } void t_msg_session::set_local_composing_state(t_composing_state state) { if (!remote_party.is_valid()) { // The session is not yet established return; } switch (local_composing_state) { case COMPOSING_STATE_IDLE: switch (state) { case COMPOSING_STATE_IDLE: break; case COMPOSING_STATE_ACTIVE: local_composing_state = state; local_idle_timeout = COMPOSING_LOCAL_IDLE_TIMEOUT; local_refresh_timeout = COMPOSING_LOCAL_REFRESH_TIMEOUT - 10; if (send_composing_state) { (void)phone->pub_send_im_iscomposing( user_config, remote_party.url, remote_party.display, IM_ISCOMPOSING_STATE_ACTIVE, COMPOSING_LOCAL_REFRESH_TIMEOUT); } break; default: assert(false); } break; case COMPOSING_STATE_ACTIVE: switch (state) { case COMPOSING_STATE_IDLE: local_composing_state = state; local_idle_timeout = 0; local_refresh_timeout = 0; if (send_composing_state) { (void)phone->pub_send_im_iscomposing( user_config, remote_party.url, remote_party.display, IM_ISCOMPOSING_STATE_IDLE, COMPOSING_LOCAL_REFRESH_TIMEOUT); } break; case COMPOSING_STATE_ACTIVE: local_idle_timeout = COMPOSING_LOCAL_IDLE_TIMEOUT; break; default: assert(false); } break; default: assert(false); } } void t_msg_session::set_remote_composing_state(t_composing_state state, time_t idle_timeout) { switch (remote_composing_state) { case COMPOSING_STATE_IDLE: switch (state) { case COMPOSING_STATE_IDLE: break; case COMPOSING_STATE_ACTIVE: remote_composing_state = state; remote_idle_timeout = idle_timeout; notify(); break; default: assert(false); } break; case COMPOSING_STATE_ACTIVE: switch (state) { case COMPOSING_STATE_IDLE: remote_composing_state = state; remote_idle_timeout = 0; notify(); break; case COMPOSING_STATE_ACTIVE: remote_idle_timeout = idle_timeout; break; } break; default: assert(false); } } void t_msg_session::dec_local_composing_timeout(void) { if (local_composing_state == COMPOSING_STATE_IDLE) return; local_idle_timeout--; if (local_idle_timeout == 0) { set_local_composing_state(COMPOSING_STATE_IDLE); } else { local_refresh_timeout--; if (local_refresh_timeout == 0) { local_refresh_timeout = COMPOSING_LOCAL_REFRESH_TIMEOUT - 10; if (send_composing_state) { (void)phone->pub_send_im_iscomposing( user_config, remote_party.url, remote_party.display, IM_ISCOMPOSING_STATE_ACTIVE, COMPOSING_LOCAL_REFRESH_TIMEOUT); } } } } void t_msg_session::dec_remote_composing_timeout(void) { if (remote_composing_state == COMPOSING_STATE_IDLE) return; remote_idle_timeout--; if (remote_idle_timeout == 0) { set_remote_composing_state(COMPOSING_STATE_IDLE); } } twinkle-1.10.1/src/im/msg_session.h000066400000000000000000000243441277565361200171720ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Instant message session. * SIP does not have a concept of message sessions. It's up to the * user interface to create the illusion of a session by grouping * messages between 2 users. */ #ifndef _MSG_SESSION_H #define _MSG_SESSION_H #include #include #include "id_object.h" #include "exceptions.h" #include "user.h" #include "sockets/url.h" #include "parser/media_type.h" #include "patterns/observer.h" using namespace std; namespace im { /** * Maximum length for inline text messages. Longer texts are shown * as attachments. */ const size_t MAX_INLINE_TEXT_LEN = 10240; /** Message direction. */ enum t_direction { MSG_DIR_IN, /**< Incoming. */ MSG_DIR_OUT /**< Outgoing. */ }; /** Text format. */ enum t_text_format { TXT_PLAIN, /**< Plain */ TXT_HTML, /**< HTML */ }; /** Message composing state. */ enum t_composing_state { COMPOSING_STATE_IDLE, COMPOSING_STATE_ACTIVE }; /** * Convert a state name a conveyed in an im_iscomposing body to a state. * @param state_name [in] The state name to convert. * @return The composing state. If the state name is unknown, then * COMPOSING_STATE_IDE is returned. */ t_composing_state string2composing_state(const string &state_name); /** * Convert a composing state to a string for an im_iscomposing body. * @param state [in] The composing state to convert. * @return The string. */ string composing_state2string(t_composing_state state); /** * Single message with meta information. * In the current implementation a message either contains a text message * or an attachment. * TODO: use multipart MIME to send a text message with an attachment. */ class t_msg : public t_id_object { public: string subject; /**< Subject of the message. */ string message; /**< The message text. */ t_direction direction; /**< Direction of the message. */ time_t timestamp; /**< Timestamp of the message. */ t_text_format format; /**< Text format. */ bool has_attachment; /**< Indicates if an attachment is present. */ string attachment_filename; /**< File name of stored attachment. */ t_media attachment_media; /**< Media type of attachment */ string attachment_save_as_name; /**< Suggested 'save as' filename */ /** Constructor. */ t_msg(); /** * Constructor. * Sets the timestamp to the current time. * @param msg [in] The message. * @param dir [in] Direction of the message. * @param fmt [in] Text format of the message. */ t_msg(const string &msg, t_direction dir, t_text_format fmt); /** * Add an attachment to the message. * @param filename [in] File name (full path) of the attachment. * @param media [in] Media type of the attachment. * @param save_as [in] Suggested file name for saving. */ void set_attachment(const string &filename, const t_media &media, const string &save_as); }; /** * Message session. * It's up to the user interface to create a message session and store * all messages in it. The session is just a container to store a * collection of page-mode messages. */ class t_msg_session : public patterns::t_subject { private: t_user *user_config; /**< User profile of the local user. */ t_display_url remote_party; /**< Remote party. */ list messages; /**< Messages sent/received. */ /** Indicates if a new message has been added to the list of message. */ bool new_message_added; bool error_recvd; /**< Indicates that an error has been received. */ string error_msg; /**< Received error message. */ /** Indicates that a delivery notification has been received. */ bool delivery_notification_recvd; /** Received delivery notification. */ string delivery_notification; bool msg_in_flight; /**< Indicates if an outgoing message is in flight. */ /** Indicates if a composing state indication must be sent to the remote party. */ bool send_composing_state; /** Message composing state of the local party. */ t_composing_state local_composing_state; /** Timeout in seconds till the active state of the local party expires. */ time_t local_idle_timeout; /** Timeout in seconds till a refresh of the active state indication must be sent. */ time_t local_refresh_timeout; /** Message composing state of the remote party. */ t_composing_state remote_composing_state; /** Timeout in seconds till the active state of the remote party expires. */ time_t remote_idle_timeout; public: /** * Constructor. * @param u [in] User profile of the local user. */ t_msg_session(t_user *u); /** * Constructor. * @param u [in] User profile of the local user. * @param _remote_party [in] URL of remote party. */ t_msg_session(t_user *u, t_display_url _remote_party); /** Destructor. */ ~t_msg_session(); /** @name getters */ //@{ t_user *get_user(void) const; t_display_url get_remote_party(void) const; const list &get_messages(void) const; t_composing_state get_remote_composing_state(void) const; //@} /** @name setters */ //@{ void set_user(t_user *u); void set_remote_party(const t_display_url &du); void set_send_composing_state(bool enable); //@} /** * Get the last message of the session. * @return The last message. * @throws empty_list_exception There are no messages. */ t_msg get_last_message(void); /** Check if a new message has been added. */ bool is_new_message_added(void) const; /** * Set the display name of the remote party if it is not yet set. * @param display [in] The display name to set. */ void set_display_if_empty(const string &display); /** * Add a received message to the session. * @param msg [in] The message to add. */ void recv_msg(const t_msg &msg); /** * Send a message to the remote party. * The message will be added to the list of messages. * @param message [in] Message to be sent. * @param format [in] Text format of the message. */ void send_msg(const string &message, t_text_format format); /** * Send a message with file attachment to the remote party. * The message will be added to the list of messages. * @param filename [in] Name of file to be sent. * @param media [in] Mime type of the file. * @param subject [in] Subject of message. */ void send_file(const string &filename, const t_media &media, const string &subject); /** * Set the error message of the session. * @param message [in] Error message. * @post @ref error_msg == message * @post @ref error_recvd == true */ void set_error(const string &message); /** * Check if an error has been received. * @return true, if an error has been received. * @return false, otherwise */ bool error_received(void) const; /** * Take the error message from the session. * @return Error message. * @pre @ref error_received() == true * @post @ref error_received() == false */ string take_error(void); /** * Set the delivery notification of the session. * @param notification [in] Delivery notification. * @post @ref delivery_notification == notification * @post @ref delivery_notification_recvd == true */ void set_delivery_notification(const string ¬ification); /** * Check if a delivery notification has been received. * @return true, if an error has been received. * @return false, otherwise */ bool delivery_notification_received(void) const; /** * Take the delivery notification from the session. * @return Delivery notification. * @pre @ref delivery_notification_received() == true * @post @ref delivery_notification_received() == false */ string take_delivery_notification(void); /** * Check if the session matches with a particular user and * remote party. * @param user [in] The user * @param remote_party [in] URL of the remote party * @return true, if there is a match * @return false, otherwise */ bool match(t_user *user, t_url _remote_party); /** * Set the message in flight indicator. * @param in_flight [in] Indicator value to set. */ void set_msg_in_flight(bool in_flight); /** * Check if a message is in flight. * @return true, message is in flight. * @return false, no message is in flight. */ bool is_msg_in_flight(void) const; /** * Set the local composing state. * If the state transitions to a new state, then a composing indication * is sent to the remote party. * The local idle timeout and refresh timeout timers are updated depending * on the current state. * @param state [in] The new local composing state. */ void set_local_composing_state(t_composing_state state); /** * Set the remote composing state. * The remote idle timeout timer is updated depending on the state. * @param state [in] The new remote composing state. * @param idle_timeout [in] The idle timeout value when state == active. * @note When state == idle, then the idle_timout argument has no meaning. */ void set_remote_composing_state(t_composing_state state, time_t idle_timeout = 120); /** * Decrement the timeout values for the local composing state if * the current state is active. * If the idle timeout reaches zero, then the state transitions * to idle, and an idle indication is sent to the remote party. * If the refresh timeout reaches zero, then an active indication * is sent to the remote party. * If the current state is idle, then nothing is done. */ void dec_local_composing_timeout(void); /** Decrement the timeout values for the remote composing state if * the current state is active. * If the idle timeout reaches zero, then the state transitions * to idle. * If the current state is idle, then nothing is done. */ void dec_remote_composing_timeout(void); }; }; // end namespace #endif twinkle-1.10.1/src/line.cpp000066400000000000000000001574321277565361200155230ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include "exceptions.h" #include "line.h" #include "log.h" #include "sdp/sdp.h" #include "util.h" #include "user.h" #include "userintf.h" #include "audits/memman.h" extern t_event_queue *evq_timekeeper; /////////////// // t_call_info /////////////// t_call_info::t_call_info() { clear(); } t_call_info::t_call_info(const t_call_info& that) { *this = that; } t_call_info& t_call_info::operator=(const t_call_info& that) { if (this != &that) { // FIXME: This may deadlock if "a=b" and "b=a" are run in // parallel. The proper solution would be to switch // to std::mutex and call std::lock(this,that). t_mutex_guard x1(that.mutex); t_mutex_guard x2(this->mutex); from_uri = that.from_uri; from_display = that.from_display; from_display_override = that.from_display_override; from_organization = that.from_organization; to_uri = that.to_uri; to_display = that.to_display; to_organization = that.to_organization; subject = that.subject; dtmf_supported = that.dtmf_supported; dtmf_inband = that.dtmf_inband; dtmf_info = that.dtmf_info; hdr_referred_by = that.hdr_referred_by; last_provisional_reason = that.last_provisional_reason; send_codec = that.send_codec; recv_codec = that.recv_codec; refer_supported = that.refer_supported; } return *this; } void t_call_info::clear(void) { t_mutex_guard g(mutex); from_uri.set_url(""); from_display.clear(); from_display_override.clear(); from_organization.clear(); to_uri.set_url(""); to_display.clear(); to_organization.clear(); subject.clear(); dtmf_supported = false; hdr_referred_by = t_hdr_referred_by(); last_provisional_reason.clear(); send_codec = CODEC_NULL; recv_codec = CODEC_NULL; refer_supported = false; } string t_call_info::get_from_display_presentation(void) const { t_mutex_guard g(mutex); if (from_display_override.empty()) { return from_display; } else { return from_display_override; } } /////////// // t_line /////////// /////////// // Private /////////// t_dialog *t_line::match_response(t_response *r, const list &l) const { list::const_iterator i; for (i = l.begin(); i != l.end(); i++) { if ((*i)->match_response(r, 0)) return *i; } return NULL; } t_dialog *t_line::match_response(StunMessage *r, t_tuid tuid, const list &l) const { list::const_iterator i; for (i = l.begin(); i != l.end(); i++) { if ((*i)->match_response(r, tuid)) return *i; } return NULL; } t_dialog *t_line::match_call_id_tags(const string &call_id, const string &to_tag, const string &from_tag, const list &l) const { list::const_iterator i; for (i = l.begin(); i != l.end(); i++) { if ((*i)->match(call_id, to_tag, from_tag)) return *i; } return NULL; } t_dialog *t_line::get_dialog(t_object_id did) const { list::const_iterator i; if (did == 0) return NULL; if (open_dialog && open_dialog->get_object_id() == did) { return open_dialog; } if (active_dialog && active_dialog->get_object_id() == did) { return active_dialog; } for (i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) { if ((*i)->get_object_id() == did) return *i; } for (i = dying_dialogs.begin(); i != dying_dialogs.end(); i++) { if ((*i)->get_object_id() == did) return *i; } return NULL; } void t_line::cleanup(void) { list::iterator i; if (open_dialog && open_dialog->get_state() == DS_TERMINATED) { MEMMAN_DELETE(open_dialog); delete open_dialog; open_dialog = NULL; } if (active_dialog && active_dialog->get_state() == DS_TERMINATED) { MEMMAN_DELETE(active_dialog); delete active_dialog; active_dialog = NULL; stop_timer(LTMR_INVITE_COMP); stop_timer(LTMR_NO_ANSWER); // If the call has been ended within 64*T1 seconds // after the reception of the first 2XX response, there // might still be open and pending dialogs. To be nice these // dialogs should be kept till the 64*T1 timer expires. // This complicates the setup of new call however. For // now the dialogs will be killed. If a slow UAS // still responds, it has bad luck and will time out. // // TODO: // A nice solution would be to move the pending and open // dialog to the dying dialog and start a new time 64*T1 // timer to keep the dying dialogs alive. A sequence of // a few short calls would add to the dying dialogs and // keep some dialogs alive longer than necessary. This // only has an impact on resources, not on signalling. // Note that the open dialog must be appended after the // pending dialogs, otherwise all received responses for // a pending dialog will match the open dialog if that // match is tried first by match_response() for (i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) { MEMMAN_DELETE(*i); delete *i; } pending_dialogs.clear(); if (open_dialog) { MEMMAN_DELETE(open_dialog); delete open_dialog; } open_dialog = NULL; } if (active_dialog) { if (active_dialog->get_state() == DS_CONFIRMED_SUB) { // The calls have been released but a subscription is // still active. substate = LSSUB_RELEASING; } else if (active_dialog->will_release()) { substate = LSSUB_RELEASING; } } for (i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) { if ((*i)->get_state() == DS_TERMINATED) { MEMMAN_DELETE(*i); delete *i; *i = NULL; } } pending_dialogs.remove(NULL); for (i = dying_dialogs.begin(); i != dying_dialogs.end(); i++) { if ((*i)->get_state() == DS_TERMINATED) { MEMMAN_DELETE(*i); delete *i; *i = NULL; } } dying_dialogs.remove(NULL); if (!open_dialog && !active_dialog && pending_dialogs.size() == 0) { state = LS_IDLE; if (keep_seized) { substate = LSSUB_SEIZED; } else { substate = LSSUB_IDLE; } is_on_hold = false; is_muted = false; hide_user = false; cleanup_transfer_consult_state(); try_to_encrypt = false; auto_answer = false; call_info.clear(); call_history->add_call_record(call_hist_record); call_hist_record.renew(); phone_user = NULL; user_defined_ringtone.clear(); ui->cb_line_state_changed(); } } void t_line::cleanup_open_pending(void) { if (open_dialog) { MEMMAN_DELETE(open_dialog); delete open_dialog; open_dialog = NULL; } list::iterator i; for (i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) { MEMMAN_DELETE(*i); delete *i; } pending_dialogs.clear(); if (!active_dialog) { is_on_hold = false; is_muted = false; hide_user = false; cleanup_transfer_consult_state(); try_to_encrypt = false; auto_answer = false; state = LS_IDLE; if (keep_seized) { substate = LSSUB_SEIZED; } else { substate = LSSUB_IDLE; } call_info.clear(); call_history->add_call_record(call_hist_record); call_hist_record.renew(); phone_user = NULL; user_defined_ringtone.clear(); ui->cb_line_state_changed(); } } void t_line::cleanup_forced(void) { list::iterator i; if (open_dialog) { MEMMAN_DELETE(open_dialog); delete open_dialog; open_dialog = NULL; } if (active_dialog) { MEMMAN_DELETE(active_dialog); delete active_dialog; active_dialog = NULL; } for (i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) { MEMMAN_DELETE(*i); delete *i; *i = NULL; } pending_dialogs.remove(NULL); for (i = dying_dialogs.begin(); i != dying_dialogs.end(); i++) { MEMMAN_DELETE(*i); delete *i; *i = NULL; } dying_dialogs.remove(NULL); // TODO: stop running timers? state = LS_IDLE; substate = LSSUB_IDLE; keep_seized = false; is_on_hold = false; is_muted = false; hide_user = false; cleanup_transfer_consult_state(); auto_answer = false; call_info.clear(); call_history->add_call_record(call_hist_record); call_hist_record.renew(); phone_user = NULL; user_defined_ringtone.clear(); ui->cb_line_state_changed(); } void t_line::cleanup_transfer_consult_state(void) { if (is_transfer_consult) { t_line *from_line = phone->get_line(consult_transfer_from_line); from_line->set_to_be_transferred(false, 0); is_transfer_consult = false; } if (to_be_transferred) { t_line *to_line = phone->get_line(consult_transfer_to_line); to_line->set_is_transfer_consult(false, 0); to_be_transferred = false; } } /////////// // Public /////////// t_line::t_line(t_phone *_phone, unsigned short _line_number) : t_id_object() { // NOTE: The rtp_port attribute can only be initialized when // a user profile has been selected. phone = _phone; state = LS_IDLE; substate = LSSUB_IDLE; open_dialog = NULL; active_dialog = NULL; is_on_hold = false; is_muted = false; hide_user = false; is_transfer_consult = false; to_be_transferred = false; try_to_encrypt = false; auto_answer = false; line_number = _line_number; id_invite_comp = 0; id_no_answer = 0; phone_user = NULL; user_defined_ringtone.clear(); keep_seized = false; } t_line::~t_line() { list::iterator i; // Stop timers if (id_invite_comp) stop_timer(LTMR_INVITE_COMP); if (id_no_answer) stop_timer(LTMR_NO_ANSWER); // Delete pointers if (open_dialog) { MEMMAN_DELETE(open_dialog); delete open_dialog; } if (active_dialog) { MEMMAN_DELETE(active_dialog); delete active_dialog; } // Delete dialogs for (i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) { MEMMAN_DELETE(*i); delete *i; } for (i = dying_dialogs.begin(); i != dying_dialogs.end(); i++) { MEMMAN_DELETE(*i); delete *i; } } t_line_state t_line::get_state(void) const { return state; } t_line_substate t_line::get_substate(void) const { return substate; } t_refer_state t_line::get_refer_state(void) const { if (active_dialog) return active_dialog->refer_state; return REFST_NULL; } void t_line::start_timer(t_line_timer timer, t_object_id did) { t_tmr_line *t; t_dialog *dialog = get_dialog(did); unsigned long dur; assert(phone_user); switch(timer) { case LTMR_ACK_TIMEOUT: assert(dialog); // RFC 3261 13.3.1.4 if (dialog->dur_ack_timeout == 0) { dialog->dur_ack_timeout = DURATION_T1; } else { dialog->dur_ack_timeout *= 2; if (dialog->dur_ack_timeout > DURATION_T2 ) { dialog->dur_ack_timeout = DURATION_T2; } } t = new t_tmr_line(dialog->dur_ack_timeout , timer, get_object_id(), did); MEMMAN_NEW(t); dialog->id_ack_timeout = t->get_object_id(); break; case LTMR_ACK_GUARD: assert(dialog); // RFC 3261 13.3.1.4 t = new t_tmr_line(64 * DURATION_T1, timer, get_object_id(), did); MEMMAN_NEW(t); dialog->id_ack_guard = t->get_object_id(); break; case LTMR_INVITE_COMP: // RFC 3261 13.2.2.4 t = new t_tmr_line(64 * DURATION_T1, timer, get_object_id(), did); MEMMAN_NEW(t); id_invite_comp = t->get_object_id(); break; case LTMR_NO_ANSWER: t = new t_tmr_line(DUR_NO_ANSWER(phone_user->get_user_profile()), timer, get_object_id(), did); MEMMAN_NEW(t); id_no_answer = t->get_object_id(); break; case LTMR_RE_INVITE_GUARD: assert(dialog); t = new t_tmr_line(DUR_RE_INVITE_GUARD, timer, get_object_id(), did); MEMMAN_NEW(t); dialog->id_re_invite_guard = t->get_object_id(); break; case LTMR_GLARE_RETRY: assert(dialog); if (dialog->is_call_id_owner()) { dur = DUR_GLARE_RETRY_OWN; } else { dur = DUR_GLARE_RETRY_NOT_OWN; } t = new t_tmr_line(dur, timer, get_object_id(), did); MEMMAN_NEW(t); dialog->id_glare_retry = t->get_object_id(); break; case LTMR_100REL_TIMEOUT: assert(dialog); // RFC 3262 3 if (dialog->dur_100rel_timeout == 0) { dialog->dur_100rel_timeout = DUR_100REL_TIMEOUT; } else { dialog->dur_100rel_timeout *= 2; } t = new t_tmr_line(dialog->dur_100rel_timeout , timer, get_object_id(), did); MEMMAN_NEW(t); dialog->id_100rel_timeout = t->get_object_id(); break; case LTMR_100REL_GUARD: assert(dialog); // RFC 3262 3 t = new t_tmr_line(DUR_100REL_GUARD, timer, get_object_id(), did); MEMMAN_NEW(t); dialog->id_100rel_guard = t->get_object_id(); break; case LTMR_CANCEL_GUARD: assert(dialog); t = new t_tmr_line(DUR_CANCEL_GUARD, timer, get_object_id(), did); MEMMAN_NEW(t); dialog->id_cancel_guard = t->get_object_id(); break; default: assert(false); } evq_timekeeper->push_start_timer(t); MEMMAN_DELETE(t); delete t; } void t_line::stop_timer(t_line_timer timer, t_object_id did) { t_object_id *id; t_dialog *dialog = get_dialog(did); switch(timer) { case LTMR_ACK_TIMEOUT: assert(dialog); dialog->dur_ack_timeout = 0; id = &dialog->id_ack_timeout; break; case LTMR_ACK_GUARD: assert(dialog); id = &dialog->id_ack_guard; break; case LTMR_INVITE_COMP: id = &id_invite_comp; break; case LTMR_NO_ANSWER: id = &id_no_answer; break; case LTMR_RE_INVITE_GUARD: assert(dialog); id = &dialog->id_re_invite_guard; break; case LTMR_GLARE_RETRY: assert(dialog); id = &dialog->id_glare_retry; break; case LTMR_100REL_TIMEOUT: assert(dialog); dialog->dur_100rel_timeout = 0; id = &dialog->id_100rel_timeout; break; case LTMR_100REL_GUARD: assert(dialog); id = &dialog->id_100rel_guard; break; case LTMR_CANCEL_GUARD: assert(dialog); id = &dialog->id_cancel_guard; // KLUDGE if (*id == 0) { // Cancel is always sent on the open dialog. // The timer is probably stopped from a pending dialog, // therefore the timer is stopped on the wrong dialog. // Check if the open dialog has a CANCEL guard timer. if (open_dialog) id = &open_dialog->id_cancel_guard; } break; default: assert(false); } if (*id != 0) evq_timekeeper->push_stop_timer(*id); *id = 0; } void t_line::invite(t_phone_user *pu, const t_url &to_uri, const string &to_display, const string &subject, bool no_fork, bool anonymous) { t_hdr_request_disposition hdr_request_disposition; if (no_fork) { hdr_request_disposition.set_fork_directive( t_hdr_request_disposition::NO_FORK); } invite(pu, to_uri, to_display, subject, t_hdr_referred_by(), t_hdr_replaces(), t_hdr_require(), hdr_request_disposition, anonymous); } void t_line::invite(t_phone_user *pu, const t_url &to_uri, const string &to_display, const string &subject, const t_hdr_referred_by &hdr_referred_by, const t_hdr_replaces &hdr_replaces, const t_hdr_require &hdr_require, const t_hdr_request_disposition &hdr_request_disposition, bool anonymous) { assert(pu); // Ignore if line is not idle if (state != LS_IDLE) { return; } assert(!open_dialog); // Validate speaker and mic string error_msg; if (!sys_config->exec_audio_validation(false, true, true, error_msg)) { ui->cb_show_msg(error_msg, MSG_CRITICAL); return; } phone_user = pu; t_user *user_config = pu->get_user_profile(); call_info.mutex.lock(); call_info.from_uri = create_user_uri(); // NOTE: hide_user is not set yet call_info.from_display = user_config->get_display(false); call_info.from_organization = user_config->get_organization(); call_info.to_uri = to_uri; call_info.to_display = to_display; call_info.to_organization.clear(); call_info.subject = subject; call_info.hdr_referred_by = hdr_referred_by; call_info.mutex.unlock(); try_to_encrypt = user_config->get_zrtp_enabled(); state = LS_BUSY; substate = LSSUB_OUTGOING_PROGRESS; hide_user = anonymous; ui->cb_line_state_changed(); open_dialog = new t_dialog(this); MEMMAN_NEW(open_dialog); open_dialog->send_invite(to_uri, to_display, subject, hdr_referred_by, hdr_replaces, hdr_require, hdr_request_disposition, anonymous); cleanup(); } void t_line::answer(void) { // Ignore if line is idle if (state == LS_IDLE) return; assert(active_dialog); // Validate speaker and mic string error_msg; if (!sys_config->exec_audio_validation(false, true, true, error_msg)) { ui->cb_show_msg(error_msg, MSG_CRITICAL); return; } stop_timer(LTMR_NO_ANSWER); try { substate = LSSUB_ANSWERING; ui->cb_line_state_changed(); active_dialog->answer(); } catch (t_exception x) { // TODO: there is no call to answer } cleanup(); } void t_line::reject(void) { // Ignore if line is idle if (state == LS_IDLE) return; assert(active_dialog); stop_timer(LTMR_NO_ANSWER); try { active_dialog->reject(R_603_DECLINE); } catch (t_exception x) { // TODO: there is no call to reject } cleanup(); } void t_line::redirect(const list &destinations, int code, string reason) { // Ignore if line is idle if (state == LS_IDLE) return; assert(active_dialog); stop_timer(LTMR_NO_ANSWER); try { active_dialog->redirect(destinations, code, reason); } catch (t_exception x) { // TODO: there is no call to redirect } cleanup(); } void t_line::end_call(void) { // Ignore if phone is idle if (state == LS_IDLE) return; if (active_dialog) { substate = LSSUB_RELEASING; ui->cb_line_state_changed(); ui->cb_stop_call_notification(line_number); active_dialog->send_bye(); // If the line was part of a transfer with consultation, // then clean the consultation state as the transfer cannot // proceed anymore. cleanup_transfer_consult_state(); cleanup(); return; } // Always send the CANCEL on the open dialog. // The pending dialogs will be cleared when the INVITE gets // terminated. // CANCEL is send on the open dialog as the CANCEL must have // the same tags as the INVITE. if (open_dialog) { substate = LSSUB_RELEASING; ui->cb_line_state_changed(); ui->cb_stop_call_notification(line_number); open_dialog->send_cancel(!pending_dialogs.empty()); // Make sure dialog is terminated if CANCEL glares with // 2XX on INVITE. for (list::iterator i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) { (*i)->set_end_after_2xx_invite(true); } cleanup(); return; } // NOTE: // The call is only ended for real when the dialog reaches // the DS_TERMINATED state, i.e. a 200 OK on BYE is received // or a 487 TERMINATED on INVITE is received. } void t_line::send_dtmf(char digit, bool inband, bool info) { // DTMF may be sent on an early media session, so find // a dialog that has an RTP session. There can be at most 1. t_dialog *d = get_dialog_with_active_session(); if (d) { d->send_dtmf(digit, inband, info); cleanup(); return; } } void t_line::options(void) { if (active_dialog && active_dialog->get_state() == DS_CONFIRMED) { active_dialog->send_options(); cleanup(); return; } } bool t_line::hold(bool rtponly) { if (is_on_hold) return true; if (active_dialog && active_dialog->get_state() == DS_CONFIRMED) { active_dialog->hold(rtponly); is_on_hold = true; ui->cb_line_state_changed(); cleanup(); return true; } return false; } void t_line::retrieve(void) { if (!is_on_hold) return; if (active_dialog && active_dialog->get_state() == DS_CONFIRMED) { active_dialog->retrieve(); is_on_hold = false; ui->cb_line_state_changed(); cleanup(); return; } } void t_line::kill_rtp(void) { if (active_dialog) active_dialog->kill_rtp(); for (list::iterator i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) { (*i)->kill_rtp(); } for (list::iterator i = dying_dialogs.begin(); i != dying_dialogs.end(); i++) { (*i)->kill_rtp(); } } void t_line::refer(const t_url &uri, const string &display) { if (active_dialog && active_dialog->get_state() == DS_CONFIRMED) { active_dialog->send_refer(uri, display); ui->cb_line_state_changed(); cleanup(); return; } } void t_line::mute(bool enable) { is_muted = enable; } void t_line::recvd_provisional(t_response *r, t_tuid tuid, t_tid tid) { t_dialog *d; if (active_dialog && active_dialog->match_response(r, 0)) { active_dialog->recvd_response(r, tuid, tid); cleanup(); return; } d = match_response(r, pending_dialogs); if (d) { d->recvd_response(r, tuid, tid); cleanup(); return; } d = match_response(r, dying_dialogs); if (d) { d->recvd_response(r, tuid, tid); cleanup(); return; } if (open_dialog && open_dialog->match_response(r, tuid)) { if (r->hdr_cseq.method == INVITE) { if (r->hdr_to.tag.size() > 0) { // Create a new pending dialog d = open_dialog->copy(); pending_dialogs.push_back(d); d->recvd_response(r, tuid, tid); } else { open_dialog->recvd_response(r, tuid, tid); } } else { open_dialog->recvd_response(r, tuid, tid); } cleanup(); return; } // out-of-dialog response // Provisional responses should only be given for INVITE. // A response for an INVITE is always in a dialog. // Ignore provisional responses for other requests. } void t_line::recvd_success(t_response *r, t_tuid tuid, t_tid tid) { t_dialog *d; if (active_dialog && active_dialog->match_response(r, 0)) { active_dialog->recvd_response(r, tuid, tid); cleanup(); return; } d = match_response(r, pending_dialogs); if (d) { d->recvd_response(r, tuid, tid); if (r->hdr_cseq.method == INVITE) { if (!active_dialog) { // Make the dialog the active dialog active_dialog = d; pending_dialogs.remove(d); start_timer(LTMR_INVITE_COMP); substate = LSSUB_ESTABLISHED; ui->cb_line_state_changed(); } else { // An active dialog already exists. // Terminate this dialog by sending BYE d->send_bye(); } } cleanup(); return; } d = match_response(r, dying_dialogs); if (d) { d->recvd_response(r, tuid, tid); if (r->hdr_cseq.method == INVITE) { d->send_bye(); } cleanup(); return; } if (open_dialog && open_dialog->match_response(r, tuid)) { if (r->hdr_cseq.method == INVITE) { // Create a new dialog d = open_dialog->copy(); if (!active_dialog) { active_dialog = d; active_dialog->recvd_response(r, tuid, tid); start_timer(LTMR_INVITE_COMP); substate = LSSUB_ESTABLISHED; ui->cb_line_state_changed(); } else { pending_dialogs.push_back(d); d->recvd_response(r, tuid, tid); // An active dialog already exists. // Terminate this dialog by sending BYE d->send_bye(); } } else { open_dialog->recvd_response(r, tuid, tid); } cleanup(); return; } // Response does not match with any pending request. Discard. } void t_line::recvd_redirect(t_response *r, t_tuid tuid, t_tid tid) { t_dialog *d; assert(phone_user); t_user *user_config = phone_user->get_user_profile(); if (active_dialog) { // If an active dialog exists then non-2XX should // only be for this dialog. if (active_dialog->match_response(r, 0)) { // Redirection of mid-dialog request if (!user_config->get_allow_redirection() || !active_dialog->redirect_request(r)) { // Redirection not allowed/failed active_dialog->recvd_response(r, tuid, tid); } // Retrieve a held line after a REFER failure if (r->hdr_cseq.method == REFER && active_dialog->out_refer_req_failed) { active_dialog->out_refer_req_failed = false; if (phone->get_active_line() == line_number && user_config->get_referrer_hold()) { retrieve(); } } } cleanup(); return; } d = match_response(r, pending_dialogs); if (d) { d->recvd_response(r, tuid, tid); if (r->hdr_cseq.method == INVITE) { pending_dialogs.remove(d); MEMMAN_DELETE(d); delete d; // RFC 3261 13.2.2.3 // All early dialogs are considered terminated // upon reception of the non-2xx final response. list::iterator i; for (i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) { MEMMAN_DELETE(*i); delete *i; } pending_dialogs.clear(); if (open_dialog) { if (!user_config->get_allow_redirection() || !open_dialog->redirect_invite(r)) { MEMMAN_DELETE(open_dialog); delete open_dialog; open_dialog = NULL; } } } cleanup(); return; } d = match_response(r, dying_dialogs); if (d) { d->recvd_response(r, tuid, tid); cleanup(); return; } if (open_dialog && open_dialog->match_response(r, tuid)) { if (r->hdr_cseq.method != INVITE) { // TODO: can there be a non-INVITE response for an // open dialog?? open_dialog->recvd_response(r, tuid, tid); } if (r->hdr_cseq.method == INVITE) { if (!user_config->get_allow_redirection() || !open_dialog->redirect_invite(r)) { // Redirection failed/not allowed open_dialog->recvd_response(r, tuid, tid); MEMMAN_DELETE(open_dialog); delete open_dialog; open_dialog = NULL; } // RFC 3261 13.2.2.3 // All early dialogs are considered terminated // upon reception of the non-2xx final response. list::iterator i; for (i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) { MEMMAN_DELETE(*i); delete *i; } pending_dialogs.clear(); } cleanup(); return; } // out-of-dialog responses should be handled by the phone } void t_line::recvd_client_error(t_response *r, t_tuid tuid, t_tid tid) { t_dialog *d; assert(phone_user); t_user *user_config = phone_user->get_user_profile(); if (active_dialog) { // If an active dialog exists then non-2XX should // only be for this dialog. if (active_dialog->match_response(r, 0)) { bool response_processed = false; if (r->must_authenticate()) { // Authentication for mid-dialog request if (active_dialog->resend_request_auth(r)) { // Authorization successul. // The response does not need to be // processed any further response_processed = true; } } if (!response_processed) { // The request failed, redirect it if there // are other destinations available. if (!user_config->get_allow_redirection() || !active_dialog->redirect_request(r)) { // Request failed active_dialog-> recvd_response(r, tuid, tid); } } // Retrieve a held line after a REFER failure if (r->hdr_cseq.method == REFER && active_dialog->out_refer_req_failed) { active_dialog->out_refer_req_failed = false; if (phone->get_active_line() == line_number && user_config->get_referrer_hold()) { retrieve(); } } } cleanup(); return; } d = match_response(r, pending_dialogs); if (d) { if (r->hdr_cseq.method != INVITE) { if (r->must_authenticate()) { // Authentication for non-INVITE request in pending dialog if (!d->resend_request_auth(r)) { // Could not authorize, send response to dialog // where it will be handle as a client failure. d->recvd_response(r, tuid, tid); } } else { d->recvd_response(r, tuid, tid); } } else { d->recvd_response(r, tuid, tid); pending_dialogs.remove(d); MEMMAN_DELETE(d); delete d; // RFC 3261 13.2.2.3 // All early dialogs are considered terminated // upon reception of the non-2xx final response. list::iterator i; for (i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) { MEMMAN_DELETE(*i); delete *i; } pending_dialogs.clear(); if (open_dialog) { bool response_processed = false; if (r->must_authenticate()) { // INVITE authentication if (open_dialog->resend_invite_auth(r)) { // Authorization successul. // The response does not need to // be processed any further response_processed = true; } } // Resend INVITE if the response indicated that // required extensions are not supported. if (!response_processed && open_dialog->resend_invite_unsupported(r)) { response_processed = true; } if (!response_processed) { // The request failed, redirect it if there // are other destinations available. if (!user_config->get_allow_redirection() || !open_dialog->redirect_invite(r)) { // Request failed MEMMAN_DELETE(open_dialog); delete open_dialog; open_dialog = NULL; } } } } cleanup(); return; } d = match_response(r, dying_dialogs); if (d) { d->recvd_response(r, tuid, tid); cleanup(); return; } if (open_dialog && open_dialog->match_response(r, tuid)) { // If the response is a 401/407 then do not send the // response to the dialog as the request must be resent. // For an INVITE request, the transaction layer has already // sent ACK for a failure response. if (r->hdr_cseq.method != INVITE) { if (r->must_authenticate()) { // Authenticate non-INVITE request if (!open_dialog->resend_request_auth(r)) { // Could not authorize, handle as other client // errors. open_dialog->recvd_response(r, tuid, tid); } } else { open_dialog->recvd_response(r, tuid, tid); } } if (r->hdr_cseq.method == INVITE) { bool response_processed = false; if (r->must_authenticate()) { // INVITE authentication if (open_dialog->resend_invite_auth(r)) { // Authorization successul. // The response does not need to // be processed any further response_processed = true; } } // Resend INVITE if the response indicated that // required extensions are not supported. if (!response_processed && open_dialog->resend_invite_unsupported(r)) { response_processed = true; } if (!response_processed) { // The request failed, redirect it if there // are other destinations available. if (!user_config->get_allow_redirection() || !open_dialog->redirect_invite(r)) { // Request failed open_dialog->recvd_response(r, tuid, tid); MEMMAN_DELETE(open_dialog); delete open_dialog; open_dialog = NULL; } } // RFC 3261 13.2.2.3 // All early dialogs are considered terminated // upon reception of the non-2xx final response. list::iterator i; for (i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) { MEMMAN_DELETE(*i); delete *i; } pending_dialogs.clear(); } cleanup(); return; } // out-of-dialog responses should be handled by the phone } void t_line::recvd_server_error(t_response *r, t_tuid tuid, t_tid tid) { t_dialog *d; assert(phone_user); t_user *user_config = phone_user->get_user_profile(); if (active_dialog) { // If an active dialog exists then non-2XX should // only be for this dialog. if (active_dialog->match_response(r, 0)) { bool response_processed = false; if (r->code == R_503_SERVICE_UNAVAILABLE) { // RFC 3263 4.3 // Failover to next destination if (active_dialog->failover_request(r)) { // Failover successul. // The response does not need to be // processed any further response_processed = true; } } if (!response_processed) { // The request failed, redirect it if there // are other destinations available. if (!user_config->get_allow_redirection() || !active_dialog->redirect_request(r)) { // Request failed active_dialog-> recvd_response(r, tuid, tid); } } // Retrieve a held line after a REFER failure if (r->hdr_cseq.method == REFER && active_dialog->out_refer_req_failed) { active_dialog->out_refer_req_failed = false; if (phone->get_active_line() == line_number && user_config->get_referrer_hold()) { retrieve(); } } } cleanup(); return; } d = match_response(r, pending_dialogs); if (d) { d->recvd_response(r, tuid, tid); if (r->hdr_cseq.method == INVITE) { pending_dialogs.remove(d); MEMMAN_DELETE(d); delete d; // RFC 3261 13.2.2.3 // All early dialogs are considered terminated // upon reception of the non-2xx final response. list::iterator i; for (i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) { MEMMAN_DELETE(*i); delete *i; } pending_dialogs.clear(); if (open_dialog) { bool response_processed = false; if (r->code == R_503_SERVICE_UNAVAILABLE) { // INVITE failover if (open_dialog->failover_invite()) { // Failover successul. // The response does not need to // be processed any further response_processed = true; } } if (!response_processed) { // The request failed, redirect it if there // are other destinations available. if (!user_config->get_allow_redirection() || !open_dialog->redirect_invite(r)) { // Request failed MEMMAN_DELETE(open_dialog); delete open_dialog; open_dialog = NULL; } } } } cleanup(); return; } d = match_response(r, dying_dialogs); if (d) { d->recvd_response(r, tuid, tid); cleanup(); return; } if (open_dialog && open_dialog->match_response(r, tuid)) { // If the response is a 503 then do not send the // response to the dialog as the request must be resent. // For an INVITE request, the transaction layer has already // sent ACK for a failure response. if (r->code != R_503_SERVICE_UNAVAILABLE && r->hdr_cseq.method != INVITE) { open_dialog->recvd_response(r, tuid, tid); } if (r->hdr_cseq.method == INVITE) { bool response_processed = false; if (r->code == R_503_SERVICE_UNAVAILABLE) { // INVITE failover if (open_dialog->failover_invite()) { // Failover successul. // The response does not need to // be processed any further response_processed = true; } } if (!response_processed) { // The request failed, redirect it if there // are other destinations available. if (!user_config->get_allow_redirection() || !open_dialog->redirect_invite(r)) { // Request failed open_dialog->recvd_response(r, tuid, tid); MEMMAN_DELETE(open_dialog); delete open_dialog; open_dialog = NULL; } } // RFC 3261 13.2.2.3 // All early dialogs are considered terminated // upon reception of the non-2xx final response. list::iterator i; for (i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) { MEMMAN_DELETE(*i); delete *i; } pending_dialogs.clear(); } cleanup(); return; } // out-of-dialog responses should be handled by the phone } void t_line::recvd_global_error(t_response *r, t_tuid tuid, t_tid tid) { recvd_redirect(r, tuid, tid); } void t_line::recvd_invite(t_phone_user *pu, t_request *r, t_tid tid, const string &ringtone) { t_user *user_config = NULL; switch (state) { case LS_IDLE: assert(!active_dialog); assert(r->hdr_to.tag == ""); /* // TEST ONLY // Test code to test INVITE authentication if (!r->hdr_authorization.is_populated()) { resp = r->create_response(R_401_UNAUTHORIZED); t_challenge c; c.auth_scheme = AUTH_DIGEST; c.digest_challenge.realm = "mtel.nl"; c.digest_challenge.nonce = "0123456789abcdef"; c.digest_challenge.opaque = "secret"; c.digest_challenge.algorithm = ALG_MD5; c.digest_challenge.qop_options.push_back(QOP_AUTH); c.digest_challenge.qop_options.push_back(QOP_AUTH_INT); resp->hdr_www_authenticate.set_challenge(c); send_response(resp, 0, tid); return; } */ assert(pu); phone_user = pu; user_config = phone_user->get_user_profile(); user_defined_ringtone = ringtone; call_info.mutex.lock(); call_info.from_uri = r->hdr_from.uri; call_info.from_display = r->hdr_from.display; call_info.from_display_override = r->hdr_from.display_override; if (r->hdr_organization.is_populated()) { call_info.from_organization = r->hdr_organization.name; } else { call_info.from_organization.clear(); } call_info.to_uri = r->hdr_to.uri; call_info.to_display = r->hdr_to.display; call_info.to_organization.clear(); call_info.subject = r->hdr_subject.subject; call_info.mutex.unlock(); try_to_encrypt = user_config->get_zrtp_enabled(); // Check for REFER support // If the Allow header is not present then assume REFER // is supported. if (!r->hdr_allow.is_populated() || r->hdr_allow.contains_method(REFER)) { call_info.refer_supported = true; } active_dialog = new t_dialog(this); MEMMAN_NEW(active_dialog); active_dialog->recvd_request(r, 0, tid); state = LS_BUSY; substate = LSSUB_INCOMING_PROGRESS; ui->cb_line_state_changed(); start_timer(LTMR_NO_ANSWER); cleanup(); // Answer if auto answer mode is activated if (auto_answer) { // Validate speaker and mic string error_msg; if (!sys_config->exec_audio_validation(false, true, true, error_msg)) { ui->cb_display_msg(error_msg, MSG_CRITICAL); } else { answer(); } } break; case LS_BUSY: // Only re-INVITEs can be sent to a busy line assert(r->hdr_to.tag != ""); /* // TEST ONLY // Test code to test re-INVITE authentication if (!r->hdr_authorization.is_populated()) { resp = r->create_response(R_401_UNAUTHORIZED); t_challenge c; c.auth_scheme = AUTH_DIGEST; c.digest_challenge.realm = "mtel.nl"; c.digest_challenge.nonce = "0123456789abcdef"; c.digest_challenge.opaque = "secret"; c.digest_challenge.algorithm = ALG_MD5; c.digest_challenge.qop_options.push_back(QOP_AUTH); c.digest_challenge.qop_options.push_back(QOP_AUTH_INT); resp->hdr_www_authenticate.set_challenge(c); send_response(resp, 0, tid); return; } */ if (active_dialog && active_dialog->match_request(r)) { // re-INVITE active_dialog->recvd_request(r, 0, tid); cleanup(); return; } // Should not get here as phone already checked that // the request matched with this line assert(false); break; default: assert(false); } } void t_line::recvd_ack(t_request *r, t_tid tid) { if (active_dialog && active_dialog->match_request(r)) { active_dialog->recvd_request(r, 0, tid); substate = LSSUB_ESTABLISHED; ui->cb_line_state_changed(); } else { // Should not get here as phone already checked that // the request matched with this line assert(false); } cleanup(); } void t_line::recvd_cancel(t_request *r, t_tid cancel_tid, t_tid target_tid) { // A CANCEL matches a dialog if the target tid equals the tid // of the INVITE request. This will be checked by // dialog::recvd_cancel() itself. if (active_dialog) { active_dialog->recvd_cancel(r, cancel_tid, target_tid); } else { // Should not get here as phone already checked that // the request matched with this line assert(false); } cleanup(); } void t_line::recvd_bye(t_request *r, t_tid tid) { if (active_dialog && active_dialog->match_request(r)) { active_dialog->recvd_request(r, 0, tid); } else { // Should not get here as phone already checked that // the request matched with this line assert(false); } cleanup(); } void t_line::recvd_options(t_request *r, t_tid tid) { if (active_dialog && active_dialog->match_request(r)) { active_dialog->recvd_request(r, 0, tid); } else { // Should not get here as phone already checked that // the request matched with this line assert(false); } cleanup(); } void t_line::recvd_prack(t_request *r, t_tid tid) { if (active_dialog && active_dialog->match_request(r)) { active_dialog->recvd_request(r, 0, tid); } else { // Should not get here as phone already checked that // the request matched with this line assert(false); } cleanup(); } void t_line::recvd_subscribe(t_request *r, t_tid tid) { if (active_dialog && active_dialog->match_request(r)) { active_dialog->recvd_request(r, 0, tid); } else { // Should not get here as phone already checked that // the request matched with this line assert(false); } cleanup(); } void t_line::recvd_notify(t_request *r, t_tid tid) { if (active_dialog && active_dialog->match_request(r)) { active_dialog->recvd_request(r, 0, tid); } else { // Should not get here as phone already checked that // the request matched with this line assert(false); } cleanup(); } void t_line::recvd_info(t_request *r, t_tid tid) { if (active_dialog && active_dialog->match_request(r)) { active_dialog->recvd_request(r, 0, tid); } else { // Should not get here as phone already checked that // the request matched with this line assert(false); } cleanup(); } void t_line::recvd_message(t_request *r, t_tid tid) { if (active_dialog && active_dialog->match_request(r)) { active_dialog->recvd_request(r, 0, tid); } else { // Should not get here as phone already checked that // the request matched with this line assert(false); } cleanup(); } bool t_line::recvd_refer(t_request *r, t_tid tid) { bool retval = false; if (active_dialog && active_dialog->match_request(r)) { active_dialog->recvd_request(r, 0, tid); retval = active_dialog->refer_accepted; } else { // Should not get here as phone already checked that // the request matched with this line assert(false); } cleanup(); return retval; } void t_line::recvd_refer_permission(bool permission, t_request *r) { if (active_dialog && active_dialog->match_request(r)) { active_dialog->recvd_refer_permission(permission, r); } cleanup(); } void t_line::recvd_stun_resp(StunMessage *r, t_tuid tuid, t_tid tid) { t_dialog *d; if (active_dialog && active_dialog->match_response(r, tuid)) { active_dialog->recvd_stun_resp(r, tuid, tid); cleanup(); return; } if (open_dialog && open_dialog->match_response(r, tuid)) { open_dialog->recvd_stun_resp(r, tuid, tid); cleanup(); return; } d = match_response(r, tuid, pending_dialogs); if (d) { d->recvd_stun_resp(r, tuid, tid); cleanup(); return; } d = match_response(r, tuid, dying_dialogs); if (d) { d->recvd_stun_resp(r, tuid, tid); cleanup(); return; } } void t_line::failure(t_failure failure, t_tid tid) { // TODO } void t_line::timeout(t_line_timer timer, t_object_id did) { t_dialog *dialog = get_dialog(did); list cf_dest; // call forwarding destinations switch (timer) { case LTMR_ACK_TIMEOUT: // If there is no dialog then ignore the timeout if (dialog) { dialog->id_ack_timeout = 0; dialog->timeout(timer); } break; case LTMR_ACK_GUARD: // If there is no dialog then ignore the timeout if (dialog) { dialog->id_ack_guard = 0; dialog->dur_ack_timeout = 0; dialog->timeout(timer); } break; case LTMR_INVITE_COMP: id_invite_comp = 0; // RFC 3261 13.2.2.4 // The UAC core considers the INVITE transaction completed // 64*T1 seconds after the reception of the first 2XX // response. // Cleanup all open and pending dialogs cleanup_open_pending(); break; case LTMR_NO_ANSWER: // User did not answer the call. // Reject call or redirect it if CF_NOANSWER is active. // If there is no active dialog then ignore the timeout. // The timer should have been stopped already. log_file->write_report("No answer timeout", "t_line::timeout"); if (active_dialog) { assert(phone_user); t_user *user_config = phone_user->get_user_profile(); t_service *srv = phone->ref_service(user_config); if (srv->get_cf_active(CF_NOANSWER, cf_dest)) { log_file->write_report("Call redirection no answer", "t_line::timeout"); active_dialog->redirect(cf_dest, R_302_MOVED_TEMPORARILY); } else { active_dialog->reject(R_480_TEMP_NOT_AVAILABLE, REASON_480_NO_ANSWER); } ui->cb_answer_timeout(get_line_number()); } break; case LTMR_RE_INVITE_GUARD: // If there is no dialog then ignore the timeout if (dialog) { dialog->id_re_invite_guard = 0; dialog->timeout(timer); } break; case LTMR_GLARE_RETRY: // If there is no dialog then ignore the timeout if (dialog) { dialog->id_glare_retry = 0; dialog->timeout(timer); } break; case LTMR_100REL_TIMEOUT: // If there is no dialog then ignore the timeout if (dialog) { dialog->id_100rel_timeout = 0; dialog->timeout(timer); } break; case LTMR_100REL_GUARD: // If there is no dialog then ignore the timeout if (dialog) { dialog->id_100rel_guard = 0; dialog->dur_100rel_timeout = 0; dialog->timeout(timer); } break; case LTMR_CANCEL_GUARD: // If there is no dialog then ignore the timeout if (dialog) { dialog->id_cancel_guard = 0; dialog->timeout(timer); } break; default: assert(false); } cleanup(); } void t_line::timeout_sub(t_subscribe_timer timer, t_object_id did, const string &event_type, const string &event_id) { t_dialog *dialog = get_dialog(did); if (dialog) dialog->timeout_sub(timer, event_type, event_id); cleanup(); } bool t_line::match(t_response *r, t_tuid tuid) const { if (open_dialog && open_dialog->match_response(r, tuid)) { return true; } if (active_dialog && active_dialog->match_response(r, 0)) { return true; } if (match_response(r, pending_dialogs)) { return true; } if (match_response(r, dying_dialogs)) { return true; } return false; } bool t_line::match(t_request *r) const { assert(r->method != CANCEL); return (active_dialog && active_dialog->match_request(r)); } bool t_line::match_cancel(t_request *r, t_tid target_tid) const { assert(r->method == CANCEL); // A CANCEL matches a dialog if the target tid equals the tid // of the INVITE request. return (active_dialog && active_dialog->match_cancel(r, target_tid)); } bool t_line::match(StunMessage *r, t_tuid tuid) const { if (open_dialog && open_dialog->match_response(r, tuid)) { return true; } if (active_dialog && active_dialog->match_response(r, tuid)) { return true; } if (match_response(r, tuid, pending_dialogs)) { return true; } if (match_response(r, tuid, dying_dialogs)) { return true; } return false; } bool t_line::match_replaces(const string &call_id, const string &to_tag, const string &from_tag, bool no_fork_req_disposition, bool &early_matched) const { if (active_dialog && active_dialog->match(call_id, to_tag, from_tag)) { early_matched = false; return true; } // RFC 3891 3 // An early dialog only matches when it was created by the UA // As an exception to this rule we accept a match when the incoming // request contained a no-fork request disposition. This disposition // indicated that the request did not fork. The reason why RFC 3891 3 // does not allow a match is to avoid problems with forked requests. // With this exception, call transfer scenario's during ringing can // be implemented. t_dialog *d; if ((d = match_call_id_tags(call_id, to_tag, from_tag, pending_dialogs)) != NULL && (d->is_call_id_owner() || no_fork_req_disposition)) { early_matched = true; return true; } return false; } bool t_line::is_invite_retrans(t_request *r) { assert(r->method == INVITE); return (active_dialog && active_dialog->is_invite_retrans(r)); } void t_line::process_invite_retrans(void) { if (active_dialog) active_dialog->process_invite_retrans(); } string t_line::create_user_contact(const string &auto_ip) const { assert(phone_user); t_user *user_config = phone_user->get_user_profile(); return user_config->create_user_contact(hide_user, auto_ip); } string t_line::create_user_uri(void) const { assert(phone_user); t_user *user_config = phone_user->get_user_profile(); return user_config->create_user_uri(hide_user); } t_response *t_line::create_options_response(t_request *r, bool in_dialog) const { assert(phone_user); t_user *user_config = phone_user->get_user_profile(); return phone->create_options_response(user_config, r, in_dialog); } void t_line::send_response(t_response *r, t_tuid tuid, t_tid tid) { if (hide_user) { r->hdr_privacy.add_privacy(PRIVACY_ID); } phone->send_response(r, tuid, tid); } void t_line::send_request(t_request *r, t_tuid tuid) { assert(phone_user); t_user *user_config = phone_user->get_user_profile(); phone->send_request(user_config, r, tuid); } t_phone *t_line::get_phone(void) const { return phone; } unsigned short t_line::get_line_number(void) const { return line_number; } bool t_line::get_is_on_hold(void) const { return is_on_hold; } bool t_line::get_is_muted(void) const { return is_muted; } bool t_line::get_hide_user(void) const { return hide_user; } bool t_line::get_is_transfer_consult(unsigned short &lineno) const { lineno = consult_transfer_from_line; return is_transfer_consult; } void t_line::set_is_transfer_consult(bool enable, unsigned short lineno) { is_transfer_consult = enable; consult_transfer_from_line = lineno; } bool t_line::get_to_be_transferred(unsigned short &lineno) const { lineno = consult_transfer_to_line; return to_be_transferred; } void t_line::set_to_be_transferred(bool enable, unsigned short lineno) { to_be_transferred = enable; consult_transfer_to_line = lineno; } bool t_line::get_is_encrypted(void) const { t_audio_session *as = get_audio_session(); if (as) return as->get_is_encrypted(); return false; } bool t_line::get_try_to_encrypt(void) const { return try_to_encrypt; } bool t_line::get_auto_answer(void) const { return auto_answer; } void t_line::set_auto_answer(bool enable) { auto_answer = enable; } bool t_line::is_refer_succeeded(void) const { if (active_dialog) return active_dialog->refer_succeeded; return false; } bool t_line::has_media(void) const { t_session *session = get_session(); return (session && !session->receive_host.empty() && !session->dst_rtp_host.empty()); } t_url t_line::get_remote_target_uri(void) const { if (!active_dialog) return t_url(); return active_dialog->get_remote_target_uri(); } t_url t_line::get_remote_target_uri_pending(void) const { if (pending_dialogs.empty()) return t_url(); return pending_dialogs.front()->get_remote_target_uri(); } string t_line::get_remote_target_display(void) const { if (!active_dialog) return ""; return active_dialog->get_remote_target_display(); } string t_line::get_remote_target_display_pending(void) const { if (pending_dialogs.empty()) return ""; return pending_dialogs.front()->get_remote_target_display(); } t_url t_line::get_remote_uri(void) const { if (!active_dialog) return t_url(); return active_dialog->get_remote_uri(); } t_url t_line::get_remote_uri_pending(void) const { if (pending_dialogs.empty()) return t_url(); return pending_dialogs.front()->get_remote_uri(); } string t_line::get_remote_display(void) const { if (!active_dialog) return ""; return active_dialog->get_remote_display(); } string t_line::get_remote_display_pending(void) const { if (pending_dialogs.empty()) return ""; return pending_dialogs.front()->get_remote_display(); } string t_line::get_call_id(void) const { if (!active_dialog) return ""; return active_dialog->get_call_id(); } string t_line::get_call_id_pending(void) const { if (pending_dialogs.empty()) return ""; return pending_dialogs.front()->get_call_id(); } string t_line::get_local_tag(void) const { if (!active_dialog) return ""; return active_dialog->get_local_tag(); } string t_line::get_local_tag_pending(void) const { if (pending_dialogs.empty()) return ""; return pending_dialogs.front()->get_local_tag(); } string t_line::get_remote_tag(void) const { if (!active_dialog) return ""; return active_dialog->get_remote_tag(); } string t_line::get_remote_tag_pending(void) const { if (pending_dialogs.empty()) return ""; return pending_dialogs.front()->get_remote_tag(); } bool t_line::remote_extension_supported(const string &extension) const { if (!active_dialog) return false; return active_dialog->remote_extension_supported(extension); } bool t_line::seize(void) { // Only an idle line can be seized. if (substate != LSSUB_IDLE) return false; substate = LSSUB_SEIZED; ui->cb_line_state_changed(); return true; } void t_line::unseize(void) { // Only a seized line can be unseized. if (substate != LSSUB_SEIZED) return; substate = LSSUB_IDLE; ui->cb_line_state_changed(); } t_session *t_line::get_session(void) const { if (!active_dialog) return NULL; return active_dialog->get_session(); } t_audio_session *t_line::get_audio_session(void) const { if (!active_dialog) return NULL; return active_dialog->get_audio_session(); } void t_line::notify_refer_progress(t_response *r) { if (active_dialog) active_dialog->notify_refer_progress(r); } void t_line::failed_retrieve(void) { // Call retrieve failed, so line is still on-hold is_on_hold = true; ui->cb_line_state_changed(); } void t_line::failed_hold(void) { // Call hold failed, so line is not on-hold is_on_hold = false; ui->cb_line_state_changed(); } void t_line::retry_retrieve_succeeded(void) { // Retry of retrieve succeeded, so line is not on-hold anymore is_on_hold = false; ui->cb_line_state_changed(); } t_call_info t_line::get_call_info(void) const { return call_info; } void t_line::ci_set_dtmf_supported(bool supported, bool inband, bool info) { call_info.dtmf_supported = supported; call_info.dtmf_inband = inband; call_info.dtmf_info = info; } void t_line::ci_set_last_provisional_reason(const string &reason) { call_info.last_provisional_reason = reason; } void t_line::ci_set_send_codec(t_audio_codec codec) { call_info.send_codec = codec; } void t_line::ci_set_recv_codec(t_audio_codec codec) { call_info.recv_codec = codec; } void t_line::ci_set_refer_supported(bool supported) { call_info.refer_supported = supported; } void t_line::init_rtp_port(void) { rtp_port = sys_config->get_rtp_port() + line_number * 2; } unsigned short t_line::get_rtp_port(void) const { return rtp_port; } t_user *t_line::get_user(void) const { t_user *user_config = NULL; if (phone_user) { user_config = phone_user->get_user_profile(); } return user_config; } t_phone_user *t_line::get_phone_user(void) const { return phone_user; } string t_line::get_ringtone(void) const { assert(phone_user); t_user *user_config = phone_user->get_user_profile(); if (!user_defined_ringtone.empty()) { // Ring tone returned by incoming call script return user_defined_ringtone; } else if (!user_config->get_ringtone_file().empty()) { // Ring tone from user profile return user_config->get_ringtone_file(); } else if (!sys_config->get_ringtone_file().empty()) { // Ring tone from system settings return sys_config->get_ringtone_file(); } else { // Twinkle default return FILE_RINGTONE; } } void t_line::confirm_zrtp_sas(void) { t_audio_session *as = get_audio_session(); if (as && !as->get_zrtp_sas_confirmed()) { as->confirm_zrtp_sas(); ui->cb_zrtp_sas_confirmed(line_number); ui->cb_line_state_changed(); log_file->write_header("t_line::confirm_zrtp_sas"); log_file->write_raw("Line "); log_file->write_raw(line_number + 1); log_file->write_raw(": User confirmed ZRTP SAS\n"); log_file->write_footer(); } } void t_line::reset_zrtp_sas_confirmation(void) { t_audio_session *as = get_audio_session(); if (as && as->get_zrtp_sas_confirmed()) { as->reset_zrtp_sas_confirmation(); ui->cb_zrtp_sas_confirmation_reset(line_number); ui->cb_line_state_changed(); log_file->write_header("t_line::reset_zrtp_sas_confirmation"); log_file->write_raw("Line "); log_file->write_raw(line_number + 1); log_file->write_raw(": User reset ZRTP SAS confirmation\n"); log_file->write_footer(); } } void t_line::enable_zrtp(void) { t_audio_session *as = get_audio_session(); if (as) { as->enable_zrtp(); } } void t_line::zrtp_request_go_clear(void) { t_audio_session *as = get_audio_session(); if (as) { as->zrtp_request_go_clear(); } } void t_line::zrtp_go_clear_ok(void) { t_audio_session *as = get_audio_session(); if (as) { as->zrtp_go_clear_ok(); } } void t_line::force_idle(void) { cleanup_forced(); } void t_line::set_keep_seized(bool seize) { keep_seized = seize; cleanup(); } bool t_line::get_keep_seized(void) const { return keep_seized; } t_dialog *t_line::get_dialog_with_active_session(void) const { if (open_dialog && open_dialog->has_active_session()) { return open_dialog; } if (active_dialog && active_dialog->has_active_session()) { return active_dialog; } for (list::const_iterator it = pending_dialogs.begin(); it != pending_dialogs.end(); ++it) { if ((*it)->has_active_session()) { return *it; } } for (list::const_iterator it = dying_dialogs.begin(); it != dying_dialogs.end(); ++it) { if ((*it)->has_active_session()) { return *it; } } return NULL; } twinkle-1.10.1/src/line.h000066400000000000000000000441231277565361200151600ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _LINE_H #define _LINE_H #include #include #include "call_history.h" #include "dialog.h" #include "id_object.h" #include "phone.h" #include "protocol.h" #include "user.h" #include "audio/audio_codecs.h" #include "sockets/url.h" #include "parser/request.h" #include "parser/response.h" #include "stun/stun.h" using namespace std; // Forward declarations class t_dialog; class t_phone; // Info about the current call. // This info can be used by the user interface to render the // call state to the user. class t_call_info { public: mutable t_mutex mutex; t_url from_uri; string from_display; // Override of display for presentation to user, e.g. name from // address book lookup. string from_display_override; string from_organization; t_url to_uri; string to_display; string to_organization; string subject; bool dtmf_supported; bool dtmf_inband; // DTMF must be sent inband bool dtmf_info; // DTMF must be sent via SIP INFO t_hdr_referred_by hdr_referred_by; // The reason phrase of the last received provisional response // on an outgoing INVITE. string last_provisional_reason; t_audio_codec send_codec; t_audio_codec recv_codec; bool refer_supported; t_call_info(); t_call_info(const t_call_info&); t_call_info& operator=(const t_call_info&); void clear(void); // Get the from display name to show to the user. string get_from_display_presentation(void) const; }; class t_line : public t_id_object { friend class t_phone; private: t_line_state state; t_line_substate substate; bool is_on_hold; bool is_muted; bool hide_user; // Anonymous call // Indicates if a call is a consultation for a transfer bool is_transfer_consult; // The line about which this consultation handles. unsigned short consult_transfer_from_line; // Indicates if this call is to be transferred after consultation. bool to_be_transferred; // After consultation this line should be transferred to the // transfer_to_line. unsigned short consult_transfer_to_line; // Indicates if media encryption should be negotiated. bool try_to_encrypt; // Indicates if call must be auto answered bool auto_answer; // Line number (starting from 0) // The number of a line may change when it moves from the user lines // to the pool of dying lines. So a line number cannot be used as // unique line identification over longer times. unsigned short line_number; // The phone that owns this line t_phone *phone; // Dialog for which no response with a to-tag has been received. // Formally this is not a dialog yet. t_dialog *open_dialog; // Dialogs for which a response (1XX/2XX) with a to-tag has // been received. list pending_dialogs; // Outgoing call: The first dialog for which a 2XX has been received. // Incoming call: Dialog created by an incoming INVITE t_dialog *active_dialog; // Currently not used. list dying_dialogs; // Timers t_object_id id_invite_comp; t_object_id id_no_answer; // Call info t_call_info call_info; /** RTP port to be used for this line. */ unsigned short rtp_port; /** * Phone user using the line. * This member is only set when the line is not idle. * An idle line is not associated with a user. * @note The line object does not own the phone user. * Therefor the line object must never delete the phone user. */ t_phone_user *phone_user; // The incoming call script can return a specific ring tone // to be played for an incoming call. This ring tone is // stored here. If there is no specific ring tone to be played // then this attribute is empty string user_defined_ringtone; // Indicates if the line must go to seized state when it // becomes idle. bool keep_seized; // Find a dialog from the list that matches the response. t_dialog *match_response(t_response *r, const list &l) const; t_dialog *match_response(StunMessage *r, t_tuid tuid, const list &l) const; t_dialog *match_call_id_tags(const string &call_id, const string &to_tag, const string &from_tag, const list &l) const; // Get the dialog with id == did. If dialog does not exist // then NULL is returned. t_dialog *get_dialog(t_object_id did) const; // Clean up terminated dialogs void cleanup(void); // Cleanup all open and pending dialogs void cleanup_open_pending(void); // Forcefully cleanup all dialogs void cleanup_forced(void); // Cleanup state for a transfer with consultation. // If the call on this line is a consult, then the consult state of // the line that is to be transferred will be cleaned too. // If the call on this line is to be transferred, then the consult // state of the consultation line will be cleared too. void cleanup_transfer_consult_state(void); public: // Call history record t_call_record call_hist_record; t_line(t_phone *_phone, unsigned short _line_number); ~t_line(); t_line_state get_state(void) const; t_line_substate get_substate(void) const; t_refer_state get_refer_state(void) const; // Timer operations void start_timer(t_line_timer timer, t_object_id did = 0); void stop_timer(t_line_timer timer, t_object_id did = 0); /** @name Actions */ //@{ /** * Send INIVTE request. * @param pu The phone user making this call. * @param to_uri The URI to be used a request-URI and To header URI * @param to_display Display name for To header. * @param subject If not empty, this string will go into the Subject header. * @param hdr_referred_by The Reffered-By header to be put in the INVITE. * @param hdr_replaces The Replaces header to be put in the INVITE. * @param hdr_require Required extensions to be put in the Require header. * @param hdr_request_disposition Request-Disposition header to be put in the INVITE. * @param anonymous Inidicates if the INVITE should be sent anonymous. * * @pre The line is idle. */ void invite(t_phone_user *pu, const t_url &to_uri, const string &to_display, const string &subject, const t_hdr_referred_by &hdr_referred_by, const t_hdr_replaces &hdr_replaces, const t_hdr_require &hdr_require, const t_hdr_request_disposition &hdr_request_disposition, bool anonymous); /** * Send INIVTE request. * @param pu The phone user making this call. * @param to_uri The URI to be used a request-URI and To header URI * @param to_display Display name for To header. * @param subject If not empty, this string will go into the Subject header. * @param no_fork If true, put a no-fork request disposition in the outgoing INVITE * @param anonymous Inidicates if the INVITE should be sent anonymous. * * @pre The line is idle. */ void invite(t_phone_user *pu, const t_url &to_uri, const string &to_display, const string &subject, bool no_fork, bool anonymous); void answer(void); void reject(void); void redirect(const list &destinations, int code, string reason = ""); void end_call(void); void send_dtmf(char digit, bool inband, bool info); //@} // OPTIONS inside dialog void options(void); bool hold(bool rtponly = false); // returns false if call cannot be put on hold void retrieve(void); // Kill all RTP stream associated with this line void kill_rtp(void); void refer(const t_url &uri, const string &display); // Mute/unmute a call // - enable = true -> mute // - enable = false -> unmute void mute(bool enable); /** @name Handle incoming responses */ //@{ void recvd_provisional(t_response *r, t_tuid tuid, t_tid tid); void recvd_success(t_response *r, t_tuid tuid, t_tid tid); void recvd_redirect(t_response *r, t_tuid tuid, t_tid tid); void recvd_client_error(t_response *r, t_tuid tuid, t_tid tid); void recvd_server_error(t_response *r, t_tuid tuid, t_tid tid); void recvd_global_error(t_response *r, t_tuid tuid, t_tid tid); //@} /** @name Handle incoming requests */ //@{ void recvd_invite(t_phone_user *pu, t_request *r, t_tid tid, const string &ringtone); void recvd_ack(t_request *r, t_tid tid); void recvd_cancel(t_request *r, t_tid cancel_tid, t_tid target_tid); void recvd_bye(t_request *r, t_tid tid); void recvd_options(t_request *r, t_tid tid); void recvd_register(t_request *r, t_tid tid); void recvd_prack(t_request *r, t_tid tid); void recvd_subscribe(t_request *r, t_tid tid); void recvd_notify(t_request *r, t_tid tid); void recvd_info(t_request *r, t_tid tid); void recvd_message(t_request *r, t_tid tid); /** * Process REFER request. * @return true, if refer has been accepted sofar. The refer may still * be rejected by the user. * @return false, if the refer has been rejected. */ bool recvd_refer(t_request *r, t_tid tid); //@} // Handle the response from the user on the question for refer // permission. This response is received on the dialog that received // the REFER before. // The request (r) is the REFER request that was received. void recvd_refer_permission(bool permission, t_request *r); void recvd_stun_resp(StunMessage *r, t_tuid tuid, t_tid tid); void failure(t_failure failure, t_tid tid); void timeout(t_line_timer timer, t_object_id did); void timeout_sub(t_subscribe_timer timer, t_object_id did, const string &event_type, const string &event_id); // Return true if the response or request matches a dialog that // is owned by this line bool match(t_response *r, t_tuid tuid) const; bool match(t_request *r) const; bool match_cancel(t_request *r, t_tid target_tid) const; bool match(StunMessage *r, t_tuid tuid) const; /** * RFC 3891 Match info from Replaces header * Match call id, to-tag and from tag like an incoming request. * @param call_id [in] The Call ID of the Replaces header. * @param to_tag [in] to-tag of the Replaces header. * @param from_tag [in] from-tag of the Replaces header. * @param no_fork_req_disposition [in] Indicates if the incoming request * contains a no-fork request disposition. * @param early_matched [out] When a match is found, early_matched * indicates if the match was on an early dialog. * @return true if a match is found with an associated dialog. */ bool match_replaces(const string &call_id, const string &to_tag, const string &from_tag, bool no_fork_req_disposition, bool &early_matched) const; // Check if an incoming INVITE is a retransmission of an INVITE // that is already being processed by this line bool is_invite_retrans(t_request *r); // Process a retransmission of an incoming INVITE void process_invite_retrans(void); // Create user uri and contact uri string create_user_contact(const string &auto_ip) const; string create_user_uri(void) const; // Create a response to an OPTIONS request // Argument 'in-dialog' indicates if the OPTIONS response is // sent within a dialog. t_response *create_options_response(t_request *r, bool in_dialog = false) const; // Send a response/request void send_response(t_response *r, t_tuid tuid, t_tid tid); void send_request(t_request *r, t_tuid tuid); t_phone *get_phone(void) const; unsigned short get_line_number(void) const; bool get_is_on_hold(void) const; bool get_is_muted(void) const; bool get_hide_user(void) const; // If this is a transfer consult, then true will be returned and // lineno will be set to the line that must be transferred. bool get_is_transfer_consult(unsigned short &lineno) const; // When setting the transfer consult indication to true, the // line that must be transferred must be passed. void set_is_transfer_consult(bool enable, unsigned short lineno); // If this line is to be transferred after consultation, then // true will be returned and lineno will be set to the line // where this line should be transferred to. bool get_to_be_transferred(unsigned short &lineno) const; // When setting the to be transferred indication to true, the // line to which must be transferred must be passed. void set_to_be_transferred(bool enable, unsigned short lineno); bool get_is_encrypted(void) const; bool get_try_to_encrypt(void) const; bool get_auto_answer(void) const; void set_auto_answer(bool enable); bool is_refer_succeeded(void) const; bool has_media(void) const; /** @name Remote (target) uri/display */ //@{ /** * Get the remote target URI of the active dialog. * @return Remote target URI. If there is no active dialog, then an * empty URI is returned. */ t_url get_remote_target_uri(void) const; /** * Get the remote target URI of the first pending dialog. * @return Remote target URI. If there is no pending dialog, then an * empty URI is returned. */ t_url get_remote_target_uri_pending(void) const; /** * Get the remote target display name of the active dialog. * @return Remote target display name. If there is no active dialog, * then an empty string is returned. */ string get_remote_target_display(void) const; /** * Get the remote target display name of the first pending dialog. * @return Remote target display name. If there is no pending dialog, * then an empty string is returned. */ string get_remote_target_display_pending(void) const; /** * Get the remote URI of the active dialog. * @return Remote URI. If there is no active dialog, then an * empty URI is returned. */ t_url get_remote_uri(void) const; /** * Get the remote URI of the first pending dialog. * @return Remote URI. If there is no pending dialog, then an * empty URI is returned. */ t_url get_remote_uri_pending(void) const; /** * Get the remote display name of the active dialog. * @return Remote display name. If there is no active dialog, * then an empty string is returned. */ string get_remote_display(void) const; /** * Get the remote display name of the first pending dialog. * @return Remote display name. If there is no pending dialog, * then an empty string is returned. */ string get_remote_display_pending(void) const; //@} /** @name Call identification */ //@{ /** * Get the call-id of the active dialog * @return If there is no active dialog, then an empty string is returned. */ string get_call_id(void) const; /** * Get the call-id of the first pending dialog * @return If there is no pending dialog, then an empty string is returned. */ string get_call_id_pending(void) const; /** * Get the local tag of the active dialog * @return If there is no active dialog, then an empty string is returned. */ string get_local_tag(void) const; /** * Get the local tag of the first pending dialog * @return If there is no pending dialog, then an empty string is returned. */ string get_local_tag_pending(void) const; /** * Get the remote tag of the active dialog * @return If there is no active dialog, then an empty string is returned. */ string get_remote_tag(void) const; /** * Get the remote tag of the first pending dialog * @return If there is no pending dialog, then an empty string is returned. */ string get_remote_tag_pending(void) const; //@} // Returns true if the remote party of the active dialog supports // the extension. // If there is no active dialog, then false is returned. bool remote_extension_supported(const string &extension) const; // Seize the line. User wants to make an outgoing call, so // the line must be marked as busy, such that an incoming call // cannot take this line. // Returns false if seizure failed bool seize(void); // Unseize the line void unseize(void); // Return the (audio) session belonging to this line. // Returns NULL if there is no (audio) session t_session *get_session(void) const; t_audio_session *get_audio_session(void) const; void notify_refer_progress(t_response *r); // Called by dialog if retrieve/hold actions failed. void failed_retrieve(void); void failed_hold(void); // Called by dialog if retry of a retrieve after a glare (491 response) // succeeded. void retry_retrieve_succeeded(void); // Get the call info record t_call_info get_call_info(void) const; void ci_set_dtmf_supported(bool supported, bool inband = false, bool info = false); void ci_set_last_provisional_reason(const string &reason); void ci_set_send_codec(t_audio_codec codec); void ci_set_recv_codec(t_audio_codec codec); void ci_set_refer_supported(bool supported); // Initialize the RTP port for this line based on the settings // in the user profile. void init_rtp_port(void); /** Get the RTP port to be used for a call on this line. */ unsigned short get_rtp_port(void) const; /** * Get the user profile of the user using the phone. * @return a pointer to the user object owned by the line. * NOT a copy. */ t_user *get_user(void) const; /** * Get the phone user using the phone. * @return Pointer to the phone user. */ t_phone_user *get_phone_user(void) const; // Get the ring tone to be played for an incoming call string get_ringtone(void) const; // ZRTP actions void confirm_zrtp_sas(void); void reset_zrtp_sas_confirmation(void); void enable_zrtp(void); void zrtp_request_go_clear(void); void zrtp_go_clear_ok(void); // Force a line to the idle state (during termination of Twinkle) void force_idle(void); // Indicate if the line must be seized after releasing void set_keep_seized(bool seize); bool get_keep_seized(void) const; /** * Get a dialog that has an active session (RTP stream). * @return The dialog that has an active session. * @return NULL, if there is no dialog with an active session. * @note There can be at most 1 dialog with an active session. */ t_dialog *get_dialog_with_active_session(void) const; }; #endif twinkle-1.10.1/src/listener.cpp000066400000000000000000000441231277565361200164110ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include "events.h" #include "listener.h" #include "log.h" #include "sys_settings.h" #include "translator.h" #include "user.h" #include "userintf.h" #include "util.h" #include "im/im_iscomposing_body.h" #include "sockets/connection_table.h" #include "sockets/socket.h" #include "parser/parse_ctrl.h" #include "parser/sip_message.h" #include "sdp/sdp_parse_ctrl.h" #include "stun/stun.h" #include "audits/memman.h" #include "presence/pidf_body.h" extern t_phone *phone; extern t_socket_udp *sip_socket; extern t_socket_tcp *sip_socket_tcp; extern t_connection_table *connection_table; extern t_event_queue *evq_trans_mgr; extern t_event_queue *evq_trans_layer; // Minimal size of a message. Messages below this size will // be silently discarded. #define MIN_MESSAGE_SIZE 10 // Maximum number of pending TCP connections. #define TCP_BACKLOG 5 void recvd_stun_msg(char *datagram, int datagram_size, unsigned long src_addr, unsigned short src_port) { StunMessage m; if (!stunParseMessage(datagram, datagram_size, m, false)) { log_file->write_report("Received faulty STUN message", "::recvd_stun_msg", LOG_STUN, LOG_DEBUG); return; } log_file->write_header("::recvd_stun_msg", LOG_STUN); log_file->write_raw("Received from: "); log_file->write_raw(h_ip2str(src_addr)); log_file->write_raw(":"); log_file->write_raw(src_port); log_file->write_endl(); log_file->write_raw(stunMsg2Str(m)); log_file->write_footer(); evq_trans_mgr->push_stun_response(&m, 0, 0); } t_sip_body *parse_body(const string &data, const t_sip_message *msg) { if (!msg->hdr_content_type.is_populated()) { // Content-Type header is missing. Pass body // unparsed. The upper application layer will // decide what to do. t_sip_body_opaque *p = new t_sip_body_opaque(data); MEMMAN_NEW(p); return p; } if (msg->hdr_content_type.media.type == "application" && msg->hdr_content_type.media.subtype == "sdp") { // Parse SDP body return t_sdp_parser::parse(data); } else if (msg->hdr_content_type.media.type == "message" && msg->hdr_content_type.media.subtype == "sipfrag") { t_sip_body_sipfrag *b; // Parse sipfrag body (RFC 3420) try { // If the sipfrag does not contain a body itself, // then the CRLF at the end of the headers is optional! // Add an additional CRLF such that the SIP parser will // parse a sipfrag if the CRLF is not present. The SIP // parser will stop after it finds the double CRLF. So // a 3rd CRLF will not be detected by the parser (yuck). list parse_errors; t_sip_message *m = t_parser::parse(data + CRLF, parse_errors); b = new t_sip_body_sipfrag(m); MEMMAN_NEW(b); MEMMAN_DELETE(m); delete m; return b; } catch (int) { // Parsing failed, maybe because a request or status // line is not present, which is not mandatory for a // sipfrag body. Add a fake status line and try to parse // again. string tmp = "SIP/2.0 100 Trying"; tmp += CRLF; tmp += data; tmp += CRLF; list parse_errors; t_sip_message *resp = t_parser::parse(tmp, parse_errors); // Parsing succeeded. Now strip the fake header t_sip_message *m = new t_sip_message(*resp); MEMMAN_NEW(m); MEMMAN_DELETE(resp); delete (resp); b = new t_sip_body_sipfrag(m); MEMMAN_NEW(b); MEMMAN_DELETE(m); delete m; return b; } } else if (msg->hdr_content_type.media.type == "application" && msg->hdr_content_type.media.subtype == "dtmf-relay") { t_sip_body_dtmf_relay *b = new t_sip_body_dtmf_relay(); MEMMAN_NEW(b); if (b->parse(data)) return b; MEMMAN_DELETE(b); delete b; throw -1; } else if (msg->hdr_content_type.media.type == "application" && msg->hdr_content_type.media.subtype == "simple-message-summary") { t_simple_msg_sum_body *b = new t_simple_msg_sum_body(); MEMMAN_NEW(b); if (b->parse(data)) return b; MEMMAN_DELETE(b); delete b; throw -1; } else if (msg->hdr_content_type.media.type == "text" && msg->hdr_content_type.media.subtype == "plain") { t_sip_body_plain_text *b = new t_sip_body_plain_text(data); MEMMAN_NEW(b); return b; } else if (msg->hdr_content_type.media.type == "text" && msg->hdr_content_type.media.subtype == "html") { t_sip_body_html_text *b = new t_sip_body_html_text(data); MEMMAN_NEW(b); return b; } else if (msg->hdr_content_type.media.type == "application" && msg->hdr_content_type.media.subtype == "pidf+xml") { t_pidf_xml_body *b = new t_pidf_xml_body(); MEMMAN_NEW(b); if (b->parse(data)) return b; MEMMAN_DELETE(b); delete b; throw -1; } else if (msg->hdr_content_type.media.type == "application" && msg->hdr_content_type.media.subtype == "im-iscomposing+xml") { t_im_iscomposing_xml_body *b = new t_im_iscomposing_xml_body(); MEMMAN_NEW(b); if (b->parse(data)) return b; MEMMAN_DELETE(b); delete b; throw -1; } else { // Pass other bodies unparsed. The upper application // layer will decide what to do. t_sip_body_opaque *p = new t_sip_body_opaque(data); MEMMAN_NEW(p); return p; } } static void process_sip_msg(t_sip_message *msg, const string &raw_headers, const string &raw_body) { t_event_network *ev_network; string log_msg; // SIP message received log_msg = "Received from: "; log_msg += msg->src_ip_port.tostring(); log_msg += "\n"; // Parse body if (!raw_body.empty()) { // The body should only be parsed if it is complete. // NOTE: The Content-length header may be absent (UDP) if (!msg->hdr_content_length.is_populated() || msg->hdr_content_length.length == raw_body.size()) { try { msg->body = parse_body(raw_body, msg); } catch (int) { if (msg->get_type() == MSG_RESPONSE) { // Discard a SIP response if the body is malformed. log_msg += "Invalid SIP message.\n"; log_msg += "Parse error in body.\n"; log_msg += to_printable(raw_headers); log_msg += to_printable(raw_body); log_file->write_report(log_msg, "::process_sip_msg", LOG_SIP, LOG_DEBUG); return; } else { // For a SIP request with a malformed body, the // transaction layer will give an error response. // Set the invalid body indication for the transaction // layer. msg->body = new t_sip_body_opaque(); MEMMAN_NEW(msg->body); msg->body->invalid = true; } } } else { log_file->write_report("Received incomplete body", "::process_sip_msg", LOG_NORMAL, LOG_WARNING); } } log_msg += to_printable(raw_headers); log_msg += to_printable(raw_body); log_file->write_report(log_msg, "::process_sip_msg", LOG_SIP); // If the message does not satisfy the mandatory // requirements from RFC 3261, then discard. // If the error is non-fatal, then the transaction layer // will send a proper error response. // If the message is an invalid response message then // discard the message. The transaction layer cannot // handle an invalid response as it cannot send an // error message back on an answer. bool fatal; string reason; if (!msg->is_valid(fatal, reason) && (fatal || msg->get_type() == MSG_RESPONSE)) { log_file->write_header("::process_sip_msg", LOG_SIP); log_file->write_raw("Discard invalid message.\n"); log_file->write_raw(reason); log_file->write_endl(); log_file->write_footer(); return; } if (msg->get_type() == MSG_REQUEST) { // RFC 3261 18.2.1 // When the server transport receives a request over any transport, it // MUST examine the value of the "sent-by" parameter in the top Via // header field value. If the host portion of the "sent-by" parameter // contains a domain name, or if it contains an IP address that differs // from the packet source address, the server MUST add a "received" // parameter to that Via header field value. This parameter MUST // contain the source address from which the packet was received. string src_ip = h_ip2str(msg->src_ip_port.ipaddr); t_via &top_via = msg->hdr_via.via_list.front(); if (top_via.host != src_ip) { top_via.received = src_ip; log_file->write_header("::process_sip_msg", LOG_SIP); log_file->write_raw("Added via-parameter received="); log_file->write_raw(src_ip); log_file->write_endl(); log_file->write_footer(); } // RFC 3581 4 // Add rport value if requested // Add received parameter if (top_via.rport_present && top_via.rport == 0) { top_via.rport = msg->src_ip_port.port; top_via.received = src_ip; } } ev_network = new t_event_network(msg); MEMMAN_NEW(ev_network); ev_network->src_addr = msg->src_ip_port.ipaddr; ev_network->src_port = msg->src_ip_port.port; ev_network->transport = msg->src_ip_port.transport; evq_trans_mgr->push(ev_network); } void *listen_udp(void *arg) { char buf[sys_config->get_sip_max_udp_size() + 1]; int data_size; unsigned long src_addr; unsigned short src_port; t_sip_message *msg; t_event_icmp *ev_icmp; string::size_type pos_body; // position of body in msg string log_msg; // Number of consecutive non-icmp errors received int num_non_icmp_errors = 0; while(true) { try { data_size = sip_socket->recvfrom(src_addr, src_port, buf, sys_config->get_sip_max_udp_size() + 1); num_non_icmp_errors = 0; } catch (int err) { // Check if an ICMP error has been received t_icmp_msg icmp; if (sip_socket->get_icmp(icmp)) { log_msg = "Received ICMP from: "; log_msg += h_ip2str(icmp.icmp_src_ipaddr); log_msg += "\nICMP type: "; log_msg += int2str(icmp.type); log_msg += "\nICMP code: "; log_msg += int2str(icmp.code); log_msg += "\nDestination of packet causing ICMP: "; log_msg += h_ip2str(icmp.ipaddr); log_msg += ":"; log_msg += int2str(icmp.port); log_msg += "\nSocket error: "; log_msg += int2str(err); log_msg += " "; log_msg += get_error_str(err); log_file->write_report(log_msg, "::listen_udp", LOG_NORMAL); ev_icmp = new t_event_icmp(icmp); MEMMAN_NEW(ev_icmp); evq_trans_mgr->push(ev_icmp); num_non_icmp_errors = 0; } else { // Even if an ICMP message is received this code can get // executed. Sometimes the error is already present on // the socket, but the ICMP message is not yet queued. log_msg = "Failed to receive from SIP UDP socket.\n"; log_msg += "Error code: "; log_msg += int2str(err); log_msg += "\n"; log_msg += get_error_str(err); log_file->write_report(log_msg, "::listen_udp"); num_non_icmp_errors++; /* * non-ICMP errors occur when a destination on the same * subnet cannot be reached. So this code seems to be * harmful. if (num_non_icmp_errors > 100) { log_msg = "Excessive number of socket errors."; log_file->write_report(log_msg, "::listen_udp", LOG_NORMAL, LOG_CRITICAL); log_msg = TRANSLATE("Excessive number of socket errors."); ui->cb_show_msg(log_msg, MSG_CRITICAL); exit(1); } */ } continue; } // Some SIP proxies send small keep alive packets to keep // NAT bindings open. Discard such small packets as these // are not SIP or STUN messages. if (data_size < MIN_MESSAGE_SIZE) continue; // Check if this is a STUN message // The first byte of a STUN message is 0x00 or 0x01. // A SIP message is ASCII so the first byte for SIP is // never 0x00 or 0x01 if (buf[0] <= 1) { recvd_stun_msg(buf, data_size, src_addr, src_port); continue; } // A SIP message may contain a NULL character (binary body), // do not handle the buffer as a C string. string datagram(buf, data_size); // Split body from header string seperator = string(CRLF) + string(CRLF); pos_body = datagram.find(seperator); // According to RFC 3261 syntax an empty line at // the end of the headers is mandatory in all SIP messages. // Here a missing empty line is accepted, but maybe // the message should be discarded. if (pos_body != string::npos) { pos_body += seperator.size(); if (pos_body >= datagram.size()) { // No body is present pos_body = string::npos; } } // Parse SIP headers string raw_headers = datagram.substr(0, pos_body); list parse_errors; try { msg = t_parser::parse(raw_headers, parse_errors); msg->src_ip_port.ipaddr = src_addr; msg->src_ip_port.port = src_port; msg->src_ip_port.transport = "udp"; } catch (int) { // Discard malformed SIP messages. log_msg = "Invalid SIP message.\n"; log_msg += "Fatal parse error in headers.\n\n"; log_msg += to_printable(datagram); log_msg += "\n"; log_file->write_report(log_msg, "::listen_udp", LOG_SIP, LOG_DEBUG); continue; } // Log non-fatal parse errors. if (!parse_errors.empty()) { log_msg = "Parse errors:\n"; log_msg += "\n"; for (list::iterator i = parse_errors.begin(); i != parse_errors.end(); i++) { log_msg += *i; log_msg += "\n"; } log_msg += "\n"; log_file->write_report(log_msg, "::listen_udp", LOG_SIP, LOG_DEBUG); } // Get raw body string raw_body; if (pos_body != string::npos) { raw_body = datagram.substr(pos_body); } process_sip_msg(msg, raw_headers, raw_body); MEMMAN_DELETE(msg); delete msg; } log_file->write_report("UDP listener terminated.", "::listen_udp"); return NULL; } void *listen_for_data_tcp(void *arg) { string log_msg; list readable_connections; while(true) { readable_connections.clear(); readable_connections = connection_table->select_read(NULL); if (readable_connections.empty()) { // Another thread cancelled the select command. // Stop listening. break; } // NOTE: The connection table is now locked. for (list::iterator it = readable_connections.begin(); it != readable_connections.end(); ++it) { string raw_headers; string raw_body; unsigned long remote_addr; unsigned short remote_port; (*it)->get_remote_address(remote_addr, remote_port); try { bool connection_closed; (*it)->read(connection_closed); if (connection_closed) { log_msg = "Connection to "; log_msg += h_ip2str(remote_addr); log_msg += ":"; log_msg += int2str(remote_port); log_msg += " closed."; log_file->write_report(log_msg, "::listen_for_data_tcp", LOG_SIP, LOG_DEBUG); connection_table->remove_connection(*it); MEMMAN_DELETE(*it); delete *it; continue; } } catch (int err) { if (err == EAGAIN || err == EINTR) { continue; } log_msg = "Got error on socket to "; log_msg += h_ip2str(remote_addr); log_msg += ":"; log_msg += int2str(remote_port); log_msg += " - "; log_msg += get_error_str(err); log_file->write_report(log_msg, "::listen_for_data_tcp", LOG_SIP, LOG_WARNING); // Connection is broken. // Signal the transaction layer that the connection is broken for // all associated registered URI's. const list &uris = (*it)->get_registered_uri_set(); for (list::const_iterator it_uri = uris.begin(); it_uri != uris.end(); ++it_uri) { evq_trans_layer->push_broken_connection(*it_uri); } // Remove the broken connection. connection_table->remove_connection(*it); MEMMAN_DELETE(*it); delete *it; continue; } // Multiple messages may have been read in one action. // Get all SIP messages from the connection. while (true) { bool error = false; bool msg_too_large = false; t_sip_message *msg = (*it)->get_sip_msg(raw_headers, raw_body, error, msg_too_large); if (error) { // The data on the connection could not be interpreted. // Close the connection to a faulty remote end. connection_table->remove_connection(*it); MEMMAN_DELETE(*it); delete *it; break; } if (msg_too_large) { // Close the connection. The message was too long, we don't want // to receive more data. Now we have an incomplete message, // but as we most likely have all headers we can still // send an error response, so the message is processed. connection_table->remove_connection(*it); MEMMAN_DELETE(*it); delete *it; } if (!msg) { // There are no complete messages on the connection. // Stop reading from this connection. break; } process_sip_msg(msg, raw_headers, raw_body); MEMMAN_DELETE(msg); delete msg; if (msg_too_large) { // The connection is closed already. Stop reading. break; } } } connection_table->unlock(); } log_file->write_report("TCP data listener terminated.", "::listen_for_data_tcp"); return NULL; } void *listen_for_conn_requests_tcp(void *arg) { unsigned long dst_addr; unsigned short dst_port; string log_msg; while (true) { try { sip_socket_tcp->listen(TCP_BACKLOG); t_socket_tcp *tcp = sip_socket_tcp->accept(dst_addr, dst_port); t_connection *conn = new t_connection(tcp); MEMMAN_NEW(conn); connection_table->add_connection(conn); } catch (int err) { if (err == EAGAIN || err == EINTR) continue; log_file->write_header("::listen_for_conn_requests_tcp", LOG_SIP, LOG_CRITICAL); log_file->write_raw("Error on accept on TCP socket: "); log_file->write_raw(get_error_str(err)); log_file->write_endl(); log_file->write_footer(); log_msg = TRANSLATE("Cannot receive incoming TCP connections."); ui->cb_show_msg(log_msg, MSG_CRITICAL); break; } } log_file->write_report("TCP connection listener terminated.", "::listen_for_conn_requests_tcp"); return NULL; } twinkle-1.10.1/src/listener.h000066400000000000000000000021121277565361200160460ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Network listener threads */ #ifndef _H_LISTENER #define _H_LISTENER /** Thread listening on SIP UDP port */ void *listen_udp(void *arg); /** Thread listening on established TCP connections */ void *listen_for_data_tcp(void *arg); /** Thread listening for incoming TCP connection requests */ void *listen_for_conn_requests_tcp(void *arg); #endif twinkle-1.10.1/src/log.cpp000066400000000000000000000202241277565361200153410ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include "log.h" #include "sys_settings.h" #include "translator.h" #include "userintf.h" #include "user.h" #include "util.h" // Pointer allocations/de-allocations are not checked by MEMMAN as the // log file will be deleted after the MEMMAN reports are logged and hence // would show false memory leaks. extern t_userintf cli; // Main function for log viewer void *main_logview(void *arg) { while (true) { log_file->wait_for_log(); // TODO: handle situation where log file was zapped. if (ui) ui->cb_log_updated(false); } return NULL; } bool t_log::move_current_to_old(void) { string old_log = log_filename + ".old"; if (rename(log_filename.c_str(), old_log.c_str()) != 0) { return false; } return true; } t_log::t_log() { log_disabled = false; log_report_disabled = false; inform_user = false; sema_logview = NULL; thr_logview = NULL; log_filename = DIR_HOME; log_filename += "/"; log_filename += DIR_USER; log_filename += "/"; log_filename += LOG_FILENAME; // If there is a previous log file, then move that to the .old file // before zapping the current log file. (void)move_current_to_old(); log_stream = new ofstream(log_filename.c_str()); if (!*log_stream) { log_disabled = true; string err = TRANSLATE("Failed to create log file %1 ."); err = replace_first(err, "%1", log_filename); err += "\nLogging is now disabled."; if (ui) ui->cb_show_msg(err, MSG_WARNING); return; } string s = PRODUCT_NAME; s += ' '; s += PRODUCT_VERSION; s += ", "; s += PRODUCT_DATE; write_report(s, "t_log::t_log"); string options_built = sys_config->get_options_built(); if (!options_built.empty()) { s = "Built with support for: "; s += options_built; write_report(s, "t_log::t_log"); } } t_log::~t_log() { if (thr_logview) delete thr_logview; if (sema_logview) delete sema_logview; delete log_stream; } void t_log::write_report(const string &report, const string &func_name) { write_report(report, func_name, LOG_NORMAL, LOG_INFO); } void t_log::write_report(const string &report, const string &func_name, t_log_class log_class, t_log_severity severity) { if (log_disabled) return; write_header(func_name, log_class, severity); write_raw(report); write_endl(); write_footer(); } void t_log::write_header(const string &func_name) { write_header(func_name, LOG_NORMAL, LOG_INFO); } void t_log::write_header(const string &func_name, t_log_class log_class, t_log_severity severity) { if (log_disabled) return; mtx_log.lock(); if (severity == LOG_DEBUG) { if (!sys_config->get_log_show_debug()) { log_report_disabled = true; return; } } switch (log_class) { case LOG_SIP: if (!sys_config->get_log_show_sip()) { log_report_disabled = true; return; } break; case LOG_STUN: if (!sys_config->get_log_show_stun()) { log_report_disabled = true; return; } break; case LOG_MEMORY: if (!sys_config->get_log_show_memory()) { log_report_disabled = true; return; } break; default: break; } struct timeval t; struct tm tm; time_t date; gettimeofday(&t, NULL); date = t.tv_sec; ::localtime_r(&date, &tm); *log_stream << "+++ "; *log_stream << tm.tm_mday; *log_stream << "-"; *log_stream << tm.tm_mon + 1; *log_stream << "-"; *log_stream << tm.tm_year + 1900; *log_stream << " "; *log_stream << int2str(tm.tm_hour, "%02d"); *log_stream << ":"; *log_stream << int2str(tm.tm_min, "%02d"); *log_stream << ":"; *log_stream << int2str(tm.tm_sec, "%02d"); *log_stream << "."; *log_stream << ulong2str(t.tv_usec, "%06d"); *log_stream << " "; // Severity switch (severity) { case LOG_INFO: *log_stream << "INFO"; break; case LOG_WARNING: *log_stream << "WARNING"; break; case LOG_CRITICAL: *log_stream << "CRITICAL"; break; case LOG_DEBUG: *log_stream << "DEBUG"; break; default: *log_stream << "UNNKOWN"; break; } *log_stream << " "; // Message class switch (log_class) { case LOG_NORMAL: *log_stream << "NORMAL"; break; case LOG_SIP: *log_stream << "SIP"; break; case LOG_STUN: *log_stream << "STUN"; break; case LOG_MEMORY: *log_stream << "MEMORY"; break; default: *log_stream << "UNNKOWN"; break; } *log_stream << " "; *log_stream << func_name; *log_stream << endl; } void t_log::write_footer(void) { if (log_disabled) return; if (log_report_disabled) { log_report_disabled = false; mtx_log.unlock(); return; } *log_stream << "---\n\n"; log_stream->flush(); // Check if log file is still in a good state if (!log_stream->good()) { // Log file is bad, disable logging log_disabled = true; if (ui) ui->cb_display_msg("Writing to log file failed. Logging disabled.", MSG_WARNING); mtx_log.unlock(); return; } bool log_zapped = false; if (log_stream->tellp() >= sys_config->get_log_max_size() * 1000000) { *log_stream << "*** Log full. Rotate to new log file. ***\n"; log_stream->flush(); log_stream->close(); if (!move_current_to_old()) { // Failed to move log file. Disable logging if (ui) ui->cb_display_msg("Renaming log file failed. Logging disabled.", MSG_WARNING); log_disabled = true; mtx_log.unlock(); return; } delete log_stream; log_stream = new ofstream(log_filename.c_str()); if (!*log_stream) { // Failed to create a new log file. Disable logging if (ui) ui->cb_display_msg("Creating log file failed. Logging disabled.", MSG_WARNING); log_disabled = true; mtx_log.unlock(); return; } log_zapped = true; } mtx_log.unlock(); // Inform user about log update. // This code must be outside the locked region, otherwise it causes // a deadlock between the GUI and log mutexes. if (inform_user && sema_logview) sema_logview->up(); } void t_log::write_raw(const string &raw) { if (log_disabled || log_report_disabled) return; if (raw.size() < MAX_LEN_LOG_STRING) { *log_stream << to_printable(raw); } else { *log_stream << to_printable(raw.substr(0, MAX_LEN_LOG_STRING)); *log_stream << "\n\n"; *log_stream << "\n"; } } void t_log::write_raw(int raw) { if (log_disabled || log_report_disabled) return; *log_stream << raw; } void t_log::write_raw(unsigned int raw) { if (log_disabled || log_report_disabled) return; *log_stream << raw; } void t_log::write_raw(unsigned short raw) { if (log_disabled || log_report_disabled) return; *log_stream << raw; } void t_log::write_raw(unsigned long raw) { if (log_disabled || log_report_disabled) return; *log_stream << raw; } void t_log::write_raw(long raw) { if (log_disabled || log_report_disabled) return; *log_stream << raw; } void t_log::write_bool(bool raw) { if (log_disabled || log_report_disabled) return; *log_stream << (raw ? "yes" : "no"); } void t_log::write_endl(void) { if (log_disabled || log_report_disabled) return; *log_stream << endl; } string t_log::get_filename(void) const { return log_filename; } void t_log::enable_inform_user(bool on) { if (on) { if (!sema_logview) { sema_logview = new t_semaphore(0); } if (!thr_logview) { thr_logview = new t_thread(main_logview, NULL); } } else { if (thr_logview) { thr_logview->cancel(); thr_logview->join(); delete thr_logview; thr_logview = NULL; } if (sema_logview) { delete sema_logview; sema_logview = NULL; } } inform_user = on; } void t_log::wait_for_log(void) { if (sema_logview) sema_logview->down(); } twinkle-1.10.1/src/log.h000066400000000000000000000062571277565361200150200ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _LOG_H #define _LOG_H #include #include #include "threads/mutex.h" #include "threads/sema.h" #include "threads/thread.h" using namespace std; #define LOG_FILENAME "twinkle.log" // Severity of a log message enum t_log_severity { LOG_INFO, LOG_WARNING, LOG_CRITICAL, LOG_DEBUG }; // Message class enum t_log_class { LOG_NORMAL, LOG_SIP, LOG_STUN, LOG_MEMORY }; class t_log { private: /** Maximum length of a logged string (bytes) */ static const string::size_type MAX_LEN_LOG_STRING = 1024; string log_filename; ofstream *log_stream; // Mutex for exclusive acces to the log file t_mutex mtx_log; // Indicates if logging is disabled bool log_disabled; bool log_report_disabled; // Indicates if the user should be informed about log updates bool inform_user; // Indicates if new data for the log viewer is available t_semaphore *sema_logview; // Thread for updating the log viewer t_thread *thr_logview; // Move the current log file to the .old log file bool move_current_to_old(void); public: t_log(); ~t_log(); // Write a report with header and footer void write_report(const string &report, const string &func_name); // normal, info void write_report(const string &report, const string &func_name, t_log_class log_class, t_log_severity severity = LOG_INFO); // Write header // This locks the mtx_log. So you must call write footer to release // the log again! void write_header(const string &func_name); // class normal, severity info void write_header(const string &func_name, t_log_class log_class, t_log_severity severity = LOG_INFO); // Write footer // This unlocks the mtx_log. void write_footer(void); // Write raw data void write_raw(const string &raw); void write_raw(int raw); void write_raw(unsigned int raw); void write_raw(unsigned short raw); void write_raw(unsigned long raw); void write_raw(long raw); void write_bool(bool raw); // Write end of line void write_endl(void); // Return the full path name of the log file string get_filename(void) const; // Enable/disable user informs on updates void enable_inform_user(bool on); // Block till log information is available for log viewer void wait_for_log(void); }; extern t_log *log_file; #endif twinkle-1.10.1/src/main.cpp000066400000000000000000000371021277565361200155070ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include "address_book.h" #include "call_history.h" #include "events.h" #include "line.h" #include "listener.h" #include "log.h" #include "phone.h" #include "protocol.h" #include "sender.h" #include "sys_settings.h" #include "transaction_mgr.h" #include "translator.h" #include "user.h" #include "userintf.h" #include "util.h" #include "sockets/connection_table.h" #include "sockets/interfaces.h" #include "sockets/socket.h" #include "threads/thread.h" #include "utils/mime_database.h" #include "audits/memman.h" using namespace std; using namespace utils; // Class to initialize the random generator before objects of // other classes are created. Initializing just from the main function // is too late. class t_init_rand { public: t_init_rand(); }; t_init_rand::t_init_rand() { srand(time(NULL)); } // Memory manager for memory leak tracing t_memman *memman; // Initialize random generator t_init_rand init_rand; // Indicates if application is ending (because user pressed Quit) bool end_app; // Language translator t_translator *translator = NULL; // IP address on which the phone is running string user_host; // Local host name string local_hostname; // SIP UDP socket for sending and receiving signaling t_socket_udp *sip_socket; // SIP TCP socket for sending and receiving signaling t_socket_tcp *sip_socket_tcp; // SIP connection table for connection oriented transport t_connection_table *connection_table; // Event queue that is handled by the transaction manager thread // The following threads write to this queue // - UDP listener // - transaction layer // - timekeeper t_event_queue *evq_trans_mgr; // Event queue that is handled by the sender thread // The following threads write to this queue: // - phone UAS // - phone UAC // - transaction manager t_event_queue *evq_sender; // Event queue that is handled by the transaction layer thread // The following threads write to this queue // - transaction manager // - timekeeper t_event_queue *evq_trans_layer; // Event queue that is handled by the phone timekeeper thread // The following threads write into this queue // - phone UAS // - phone UAC // - transaction manager t_event_queue *evq_timekeeper; // The timekeeper t_timekeeper *timekeeper; // The transaction manager t_transaction_mgr *transaction_mgr; // The phone t_phone *phone; // User interface t_userintf *ui; // Log file t_log *log_file; // System config t_sys_settings *sys_config; // Call history t_call_history *call_history; // Local address book t_address_book *ab_local; // Mime database t_mime_database *mime_database; // If a port number is passed by the user on the command line, then // that port number overrides the port from the system settings. unsigned short g_override_sip_port = 0; unsigned short g_override_rtp_port = 0; // Indicates if LinuxThreads or NPTL is active. bool threading_is_LinuxThreads; int main(int argc, char *argv[]) { string error_msg; end_app = false; memman = new t_memman(); MEMMAN_NEW(memman); translator = new t_translator(); MEMMAN_NEW(translator); connection_table = new t_connection_table(); MEMMAN_NEW(connection_table); evq_trans_mgr = new t_event_queue(); MEMMAN_NEW(evq_trans_mgr); evq_sender = new t_event_queue(); MEMMAN_NEW(evq_sender); evq_trans_layer = new t_event_queue(); MEMMAN_NEW(evq_trans_layer); evq_timekeeper = new t_event_queue(); MEMMAN_NEW(evq_timekeeper); timekeeper = new t_timekeeper(); MEMMAN_NEW(timekeeper); transaction_mgr = new t_transaction_mgr(); MEMMAN_NEW(transaction_mgr); phone = new t_phone(); MEMMAN_NEW(phone); sys_config = new t_sys_settings(); MEMMAN_NEW(sys_config); ui = new t_userintf(phone); MEMMAN_NEW(ui); // Check requirements on environment if (!sys_config->check_environment(error_msg)) { // Environment is not good ui->cb_show_msg(error_msg, MSG_CRITICAL); exit(1); } // Read system configuration if (!sys_config->read_config(error_msg)) { ui->cb_show_msg(error_msg, MSG_CRITICAL); exit(1); } // Get default values from system configuration list config_files; list start_user_profiles = sys_config->get_start_user_profiles(); for (list::iterator i = start_user_profiles.begin(); i != start_user_profiles.end(); i++) { string config_file = *i; config_file += USER_FILE_EXT; config_files.push_back(config_file); } #if 0 // DEPRECATED if (user_host.empty()) { string ip; if (exists_interface(sys_config->get_start_user_host())) { user_host = sys_config->get_start_user_host(); } else if (exists_interface_dev(sys_config->get_start_user_nic(), ip)) { user_host = ip; } } #endif user_host = AUTO_IP4_ADDRESS; local_hostname = get_local_hostname(); // Create a lock file to guarantee that the application // runs only once. bool already_running; if (!sys_config->create_lock_file(false, error_msg, already_running)) { ui->cb_show_msg(error_msg, MSG_CRITICAL); exit(1); } log_file = new t_log(); MEMMAN_NEW(log_file); call_history = new t_call_history(); MEMMAN_NEW(call_history); // Determine threading implementation threading_is_LinuxThreads = t_thread::is_LinuxThreads(); if (threading_is_LinuxThreads) { log_file->write_report("Threading implementation is LinuxThreads.", "::main", LOG_NORMAL, LOG_INFO); } else { log_file->write_report("Threading implementation is NPTL.", "::main", LOG_NORMAL, LOG_INFO); } // Take default user profile if there are is no default is sys settings if (config_files.empty()) config_files.push_back(USER_CONFIG_FILE); // Read user configurations. if (argc >= 2) { config_files.clear(); for (int i = 1; i < argc; i++) { config_files.push_back(argv[i]); } } // Activate users for (list::iterator i = config_files.begin(); i != config_files.end(); i++) { t_user *user_config = new t_user(); MEMMAN_NEW(user_config); if (!user_config->read_config(*i, error_msg)) { ui->cb_show_msg(error_msg, MSG_CRITICAL); sys_config->delete_lock_file(); exit(1); } t_user *dup_user; if(!phone->add_phone_user(*user_config, &dup_user)) { error_msg = "The following profiles are both for user "; error_msg += user_config->get_name(); error_msg += '@'; error_msg += user_config->get_domain(); error_msg += ":\n\n"; error_msg += user_config->get_profile_name(); error_msg += "\n"; error_msg += dup_user->get_profile_name(); error_msg += "\n\n"; error_msg += "You can only run multiple profiles "; error_msg += "for different users."; ui->cb_show_msg(error_msg, MSG_CRITICAL); sys_config->delete_lock_file(); exit(1); } MEMMAN_DELETE(user_config); delete user_config; } // Read call history if (!call_history->load(error_msg)) { log_file->write_report(error_msg, "::main", LOG_NORMAL, LOG_WARNING); } // Create local address book ab_local = new t_address_book(); MEMMAN_NEW(ab_local); // Read local address book if (!ab_local->load(error_msg)) { log_file->write_report(error_msg, "::main", LOG_NORMAL, LOG_WARNING); ui->cb_show_msg(error_msg, MSG_WARNING); } // Create mime database mime_database = new t_mime_database(); MEMMAN_NEW(mime_database); if (!mime_database->load(error_msg)) { log_file->write_report(error_msg, "::main", LOG_NORMAL, LOG_WARNING); } // Initialize RTP port settings. phone->init_rtp_ports(); // Open UDP socket for SIP signaling try { sip_socket = new t_socket_udp(sys_config->get_sip_port()); MEMMAN_NEW(sip_socket); if (sip_socket->enable_icmp()) { log_file->write_report("ICMP processing enabled.", "::main"); } else { log_file->write_report("ICMP processing disabled.", "::main"); } } catch (int err) { string msg("Failed to create a UDP socket (SIP) on port "); msg += int2str(sys_config->get_sip_port()); msg += "\n"; msg += get_error_str(err); log_file->write_report(msg, "::main", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); sys_config->delete_lock_file(); exit(1); } // Open TCP socket for SIP signaling try { sip_socket_tcp = new t_socket_tcp(sys_config->get_sip_port()); MEMMAN_NEW(sip_socket_tcp); } catch (int err) { string msg("Failed to create a TCP socket (SIP) on port "); msg += int2str(sys_config->get_sip_port()); msg += "\n"; msg += get_error_str(err); log_file->write_report(msg, "::main", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); sys_config->delete_lock_file(); exit(1); } #if 0 // DEPRECATED // Pick network interface if (user_host.empty()) { user_host = ui->select_network_intf(); if (user_host.empty()) { sys_config->delete_lock_file(); exit(1); } } #endif // Discover NAT type if STUN is enabled list msg_list; if (!phone->stun_discover_nat(msg_list)) { for (list::iterator i = msg_list.begin(); i != msg_list.end(); i++) { ui->cb_show_msg(*i, MSG_WARNING); } } // Dedicated thread will catch SIGALRM, SIGINT, SIGTERM, SIGCHLD signals, // therefore all threads must block these signals. Block now, then all // created threads will inherit the signal mask. // In LinuxThreads the sigwait does not work very well, so // in LinuxThreads a signal handler is used instead. if (!threading_is_LinuxThreads) { sigset_t sigset; sigemptyset(&sigset); sigaddset(&sigset, SIGALRM); sigaddset(&sigset, SIGINT); sigaddset(&sigset, SIGTERM); sigaddset(&sigset, SIGCHLD); sigprocmask(SIG_BLOCK, &sigset, NULL); } else { if (!phone->set_sighandler()) { string msg = "Failed to register signal handler."; log_file->write_report(msg, "::main", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); sys_config->delete_lock_file(); exit(1); } } // Ignore SIGPIPE so read from broken sockets will not cause // the process to terminate. (void)signal(SIGPIPE, SIG_IGN); // Create threads t_thread *thr_sender; t_thread *thr_tcp_sender; t_thread *thr_listen_udp; t_thread *thr_listen_data_tcp; t_thread *thr_listen_conn_tcp; t_thread *thr_conn_timeout_handler; t_thread *thr_timekeeper; t_thread *thr_alarm_catcher = NULL; t_thread *thr_sig_catcher = NULL; t_thread *thr_trans_mgr; t_thread *thr_phone_uas; try { // SIP sender thread thr_sender = new t_thread(sender_loop, NULL); MEMMAN_NEW(thr_sender); // SIP TCP sender thread thr_tcp_sender = new t_thread(tcp_sender_loop, NULL); MEMMAN_NEW(thr_tcp_sender); // UDP listener thread thr_listen_udp = new t_thread(listen_udp, NULL); MEMMAN_NEW(thr_listen_udp); // TCP data listener thread thr_listen_data_tcp = new t_thread(listen_for_data_tcp, NULL); MEMMAN_NEW(thr_listen_data_tcp); // TCP connection listener thread thr_listen_conn_tcp = new t_thread(listen_for_conn_requests_tcp, NULL); MEMMAN_NEW(thr_listen_conn_tcp); // Connection timeout handler thread thr_conn_timeout_handler = new t_thread(connection_timeout_main, NULL); MEMMAN_NEW(thr_conn_timeout_handler); // Timekeeper thread thr_timekeeper = new t_thread(timekeeper_main, NULL); MEMMAN_NEW(thr_timekeeper); if (!threading_is_LinuxThreads) { // Alarm catcher thread thr_alarm_catcher = new t_thread(timekeeper_sigwait, NULL); MEMMAN_NEW(thr_alarm_catcher); // Signal catcher thread thr_sig_catcher = new t_thread(phone_sigwait, NULL); MEMMAN_NEW(thr_sig_catcher); } // Transaction manager thread thr_trans_mgr = new t_thread(transaction_mgr_main, NULL); MEMMAN_NEW(thr_trans_mgr); // Phone thread (UAS) thr_phone_uas = new t_thread(phone_uas_main, NULL); MEMMAN_NEW(thr_phone_uas); } catch (int) { string msg = "Failed to create threads."; log_file->write_report(msg, "::main", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); sys_config->delete_lock_file(); exit(1); } // Validate sound devices if (!sys_config->exec_audio_validation(true, true, true, error_msg)) { ui->cb_show_msg(error_msg, MSG_WARNING); } try { ui->run(); } catch (string e) { string msg = "Exception: "; msg += e; log_file->write_report(msg, "::main", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); sys_config->delete_lock_file(); exit(1); } catch (...) { string msg = "Unknown exception"; log_file->write_report(msg, "::main", LOG_NORMAL, LOG_CRITICAL); ui->cb_show_msg(msg, MSG_CRITICAL); sys_config->delete_lock_file(); exit(1); } // Application is ending end_app = true; // Kill the threads getting receiving input from the outside world first, // so no new inputs come in during termination. thr_listen_udp->cancel(); thr_listen_udp->join(); thr_listen_conn_tcp->cancel(); thr_listen_conn_tcp->join(); connection_table->cancel_select(); thr_listen_data_tcp->join(); thr_conn_timeout_handler->join(); thr_tcp_sender->join(); evq_trans_layer->push_quit(); thr_phone_uas->join(); evq_trans_mgr->push_quit(); thr_trans_mgr->join(); if (!threading_is_LinuxThreads) { try { thr_sig_catcher->cancel(); } catch (int) { // Thread terminated already by itself } thr_sig_catcher->join(); thr_alarm_catcher->cancel(); thr_alarm_catcher->join(); } evq_timekeeper->push_quit(); thr_timekeeper->join(); evq_sender->push_quit(); thr_sender->join(); sys_config->remove_all_tmp_files(); MEMMAN_DELETE(thr_phone_uas); delete thr_phone_uas; MEMMAN_DELETE(thr_trans_mgr); delete thr_trans_mgr; MEMMAN_DELETE(thr_timekeeper); delete thr_timekeeper; MEMMAN_DELETE(thr_conn_timeout_handler); delete thr_conn_timeout_handler; if (!threading_is_LinuxThreads) { MEMMAN_DELETE(thr_sig_catcher); delete thr_sig_catcher; MEMMAN_DELETE(thr_alarm_catcher); delete thr_alarm_catcher; } MEMMAN_DELETE(thr_listen_udp); delete thr_listen_udp; MEMMAN_DELETE(thr_sender); delete thr_sender; MEMMAN_DELETE(thr_tcp_sender); delete thr_tcp_sender; MEMMAN_DELETE(thr_listen_data_tcp); delete thr_listen_data_tcp; MEMMAN_DELETE(thr_listen_conn_tcp); delete thr_listen_conn_tcp; MEMMAN_DELETE(mime_database); delete mime_database; MEMMAN_DELETE(ab_local); delete ab_local; MEMMAN_DELETE(call_history); delete call_history; MEMMAN_DELETE(ui); delete ui; ui = NULL; MEMMAN_DELETE(connection_table); delete connection_table; MEMMAN_DELETE(sip_socket_tcp); delete sip_socket_tcp; MEMMAN_DELETE(sip_socket); delete sip_socket; MEMMAN_DELETE(phone); delete phone; MEMMAN_DELETE(transaction_mgr); delete transaction_mgr; MEMMAN_DELETE(timekeeper); delete timekeeper; MEMMAN_DELETE(evq_trans_mgr); delete evq_trans_mgr; MEMMAN_DELETE(evq_sender); delete evq_sender; MEMMAN_DELETE(evq_trans_layer); delete evq_trans_layer; MEMMAN_DELETE(evq_timekeeper); delete evq_timekeeper; MEMMAN_DELETE(translator); delete translator; translator = NULL; // Report memory leaks // Report deletion of log_file and sys_config already to get // a correct report. MEMMAN_DELETE(sys_config); MEMMAN_DELETE(log_file); MEMMAN_DELETE(memman); MEMMAN_REPORT; delete log_file; delete memman; sys_config->delete_lock_file(); delete sys_config; } twinkle-1.10.1/src/mwi/000077500000000000000000000000001277565361200146505ustar00rootroot00000000000000twinkle-1.10.1/src/mwi/CMakeLists.txt000066400000000000000000000002661277565361200174140ustar00rootroot00000000000000project(libtwinkle-mwi) set(LIBTWINKLE_MWI-SRCS mwi.cpp mwi_dialog.cpp mwi_subscription.cpp simple_msg_sum_body.cpp ) add_library(libtwinkle-mwi OBJECT ${LIBTWINKLE_MWI-SRCS}) twinkle-1.10.1/src/mwi/mwi.cpp000066400000000000000000000032411277565361200161500ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "mwi.h" t_mwi::t_mwi() : status(MWI_UNKNOWN) {} t_mwi::t_status t_mwi::get_status(void) const { t_status result; mtx_mwi.lock(); result = status; mtx_mwi.unlock(); return result; } bool t_mwi::get_msg_waiting(void) const { bool result; mtx_mwi.lock(); result = msg_waiting; mtx_mwi.unlock(); return result; } t_msg_summary t_mwi::get_voice_msg_summary(void) const { t_msg_summary result; mtx_mwi.lock(); result = voice_msg_summary; mtx_mwi.unlock(); return result; } void t_mwi::set_status(t_status _status) { mtx_mwi.lock(); status = _status; mtx_mwi.unlock(); } void t_mwi::set_msg_waiting(bool _msg_waiting) { mtx_mwi.lock(); msg_waiting = _msg_waiting; mtx_mwi.unlock(); } void t_mwi::set_voice_msg_summary(const t_msg_summary &summary) { mtx_mwi.lock(); voice_msg_summary = summary; mtx_mwi.unlock(); } void t_mwi::clear_voice_msg_summary(void) { mtx_mwi.lock(); voice_msg_summary.clear(); mtx_mwi.unlock(); } twinkle-1.10.1/src/mwi/mwi.h000066400000000000000000000033371277565361200156230ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Message Waiting Indication information. */ #ifndef _MWI_HH #define _MWI_HH #include "simple_msg_sum_body.h" #include "threads/mutex.h" /** MWI information */ class t_mwi { public: /** Status of MWI information */ enum t_status { MWI_UNKNOWN, /**< The status is unknown */ MWI_KNOWN, /**< MWI properly received */ MWI_FAILED /**< MWI subscription failed */ }; private: /** Mutex for exclusive access to MWI information */ mutable t_mutex mtx_mwi; /** MWI status */ t_status status; /** Indication if messages are waiting */ bool msg_waiting; /** Summary of voice messages waiting */ t_msg_summary voice_msg_summary; public: t_mwi(); t_status get_status(void) const; bool get_msg_waiting(void) const; t_msg_summary get_voice_msg_summary(void) const; void set_status(t_status _status); void set_msg_waiting(bool _msg_waiting); void set_voice_msg_summary(const t_msg_summary &summary); /** Set all counters to zero */ void clear_voice_msg_summary(void); }; #endif twinkle-1.10.1/src/mwi/mwi_dialog.cpp000066400000000000000000000021451277565361200174710ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "mwi_dialog.h" #include "mwi_subscription.h" #include "phone_user.h" #include "audits/memman.h" t_mwi_dialog::t_mwi_dialog(t_phone_user *_phone_user) : t_subscription_dialog(_phone_user) { subscription = new t_mwi_subscription(this, &(phone_user->mwi)); MEMMAN_NEW(subscription); } t_mwi_dialog *t_mwi_dialog::copy(void) { // Copy is not needed. assert(false); return NULL; } twinkle-1.10.1/src/mwi/mwi_dialog.h000066400000000000000000000017751277565361200171460ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _MWI_DIALOG_H #define _MWI_DIALOG_H #include "mwi.h" #include "subscription_dialog.h" // Forward declaration class t_phone_user; class t_mwi_dialog : public t_subscription_dialog { public: t_mwi_dialog(t_phone_user *_phone_user); virtual t_mwi_dialog *copy(void); }; #endif twinkle-1.10.1/src/mwi/mwi_subscription.cpp000066400000000000000000000063241277565361200207610ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "mwi_subscription.h" #include #include "userintf.h" #include "audits/memman.h" #include "parser/hdr_event.h" t_request *t_mwi_subscription::create_subscribe(unsigned long expires) const { t_request *r = t_subscription::create_subscribe(expires); SET_MWI_HDR_ACCEPT(r->hdr_accept); return r; } t_mwi_subscription::t_mwi_subscription(t_mwi_dialog *_dialog, t_mwi *_mwi) : t_subscription(_dialog, SR_SUBSCRIBER, SIP_EVENT_MSG_SUMMARY), mwi(_mwi) {} bool t_mwi_subscription::recv_notify(t_request *r, t_tuid tuid, t_tid tid) { if (t_subscription::recv_notify(r, tuid, tid)) return true; bool unsupported_body = false; // NOTE: if the subscription is still pending (RFC 3265 3.2.4), then the // information in the body has no meaning. if (r->body && r->body->get_type() == BODY_SIMPLE_MSG_SUM && !is_pending()) { t_simple_msg_sum_body *body = dynamic_cast(r->body); assert(body); mwi->set_msg_waiting(body->get_msg_waiting()); t_msg_summary summary; if (body->get_msg_summary(MSG_CONTEXT_VOICE, summary)) { mwi->set_voice_msg_summary(summary); } else { mwi->clear_voice_msg_summary(); } mwi->set_status(t_mwi::MWI_KNOWN); } // Verify if there is an usupported body. if (r->body && r->body->get_type() != BODY_SIMPLE_MSG_SUM) { unsupported_body = true; } if (state == SS_TERMINATED && !may_resubscribe) { // The MWI server ended the subscription and indicated // that resubscription is not possible. So no MWI status // can be retrieved anymore. This should not happen, so // present it as a failure to the user. mwi->set_status(t_mwi::MWI_FAILED); ui->cb_mwi_terminated(user_config, get_reason_termination()); } t_response *resp; if (unsupported_body) { resp = r->create_response(R_415_UNSUPPORTED_MEDIA_TYPE); SET_MWI_HDR_ACCEPT(r->hdr_accept); } else { resp = r->create_response(R_200_OK); } send_response(user_config, resp, 0, tid); MEMMAN_DELETE(resp); delete resp; ui->cb_update_mwi(); return true; } bool t_mwi_subscription::recv_subscribe_response(t_response *r, t_tuid tuid, t_tid tid) { // Parent handles the SUBSCRIBE response (void)t_subscription::recv_subscribe_response(r, tuid, tid); // If the subscription is terminated after the SUBSCRIBE response, it means // that subscription failed. if (state == SS_TERMINATED) { ui->cb_mwi_subscribe_failed(user_config, r, mwi->get_status() != t_mwi::MWI_FAILED); mwi->set_status(t_mwi::MWI_FAILED); } ui->cb_update_mwi(); return true; } twinkle-1.10.1/src/mwi/mwi_subscription.h000066400000000000000000000023741277565361200204270ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // RFC 3842 // message-summary subscription #ifndef _MWI_SUBSCRIPTION_H #define _MWI_SUBSCRIPTION_H #include "mwi.h" #include "mwi_dialog.h" #include "subscription.h" class t_mwi_subscription : public t_subscription { private: t_mwi *mwi; protected: virtual t_request *create_subscribe(unsigned long expires) const; public: t_mwi_subscription(t_mwi_dialog *_dialog, t_mwi *_mwi); virtual bool recv_notify(t_request *r, t_tuid tuid, t_tid tid); virtual bool recv_subscribe_response(t_response *r, t_tuid tuid, t_tid tid); }; #endif twinkle-1.10.1/src/mwi/simple_msg_sum_body.cpp000066400000000000000000000113541277565361200214200ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "simple_msg_sum_body.h" #include #include #include #include "protocol.h" #include "util.h" #include "audits/memman.h" t_msg_summary::t_msg_summary() : newmsgs(0), newmsgs_urgent(0), oldmsgs(0), oldmsgs_urgent(0) {} bool t_msg_summary::parse(const string &s) { newmsgs = 0; oldmsgs = 0; newmsgs_urgent = 0; oldmsgs_urgent = 0; // RFC 3842 5.2 // msg-summary-line = message-context-class HCOLON newmsgs SLASH oldmsgs // [ LPAREN new-urgentmsgs SLASH old-urgentmsgs RPAREN ] // This regex matches the part after HCOLON std::regex re("(\\d+)\\s*/\\s*(\\d+)(?:\\s*\\((\\d+)\\s*/\\s*(\\d+)\\s*\\))?"); std::smatch m; if (!std::regex_match(s, m, re)) return false; if (m.size() == 3) { newmsgs = std::stoul(m.str(1), NULL, 10); oldmsgs = std::stoul(m.str(2), NULL, 10); return true; } else if (m.size() == 5) { newmsgs = std::stoul(m.str(1), NULL, 10); oldmsgs = std::stoul(m.str(2), NULL, 10); newmsgs_urgent = std::stoul(m.str(3), NULL, 10); oldmsgs_urgent = std::stoul(m.str(4), NULL, 10); return true; } return false; } void t_msg_summary::clear(void) { newmsgs = 0; newmsgs_urgent = 0; oldmsgs = 0; oldmsgs_urgent = 0; } bool t_simple_msg_sum_body::is_context(const string &s) { return ( s == MSG_CONTEXT_VOICE || s == MSG_CONTEXT_FAX || s == MSG_CONTEXT_MULTIMEDIA || s == MSG_CONTEXT_TEXT || s == MSG_CONTEXT_NONE); } t_simple_msg_sum_body::t_simple_msg_sum_body() : t_sip_body() {} string t_simple_msg_sum_body::encode(void) const { string s = "Messages-Waiting: "; s += (msg_waiting ? "yes" : "no"); s += CRLF; if (msg_account.is_valid()) { s += "Message-Account: "; s += msg_account.encode(); s += CRLF; } for (t_msg_sum_const_iter i = msg_summary.begin(); i != msg_summary.end(); ++i) { const t_msg_summary &summary = i->second; s += i->first; s += ": "; s += ulong2str(summary.newmsgs); s += "/"; s += ulong2str(summary.oldmsgs); if (summary.newmsgs_urgent > 0 || summary.oldmsgs_urgent > 0) { s += " ("; s += ulong2str(summary.newmsgs_urgent); s += "/"; s += ulong2str(summary.oldmsgs_urgent); s += ")"; } s += CRLF; } return s; } t_sip_body *t_simple_msg_sum_body::copy(void) const { t_simple_msg_sum_body *body = new t_simple_msg_sum_body(*this); MEMMAN_NEW(body); return body; } t_body_type t_simple_msg_sum_body::get_type(void) const { return BODY_SIMPLE_MSG_SUM; } t_media t_simple_msg_sum_body::get_media(void) const { return t_media("application", "simple-message-summary"); } void t_simple_msg_sum_body::add_msg_summary(const string &context, const t_msg_summary summary) { msg_summary.insert(make_pair(context, summary)); } bool t_simple_msg_sum_body::get_msg_waiting(void) const { return msg_waiting; } t_url t_simple_msg_sum_body::get_msg_account(void) const { return msg_account; } bool t_simple_msg_sum_body::get_msg_summary(const string &context, t_msg_summary &summary) const { t_msg_sum_const_iter it = msg_summary.find(context); if (it == msg_summary.end()) return false; summary = it->second; return true; } bool t_simple_msg_sum_body::parse(const string &s) { bool valid = false; vector lines = split_linebreak(s); for (vector::iterator i = lines.begin(); i != lines.end(); ++i) { string line = trim(*i); if (line.empty()) continue; vector l = split_on_first(line, ':'); if (l.size() != 2) continue; string header = tolower(trim(l[0])); string value = tolower(trim(l[1])); if (value.empty()) continue; if (header == "messages-waiting") { if (value == "yes") { msg_waiting = true; valid = true; } else if (value == "no") { msg_waiting = false; valid = true; } } else if (header == "message-account") { msg_account.set_url(value); } else if (is_context(header)) { t_msg_summary summary; if (summary.parse(value)) { add_msg_summary(header, summary); } } } invalid = !valid; return valid; } twinkle-1.10.1/src/mwi/simple_msg_sum_body.h000066400000000000000000000052101277565361200210570ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * RFC 3842 simple-message-summary body */ #ifndef SIMPLE_MSG_SUM_BODY_HH #define SIMPLE_MSG_SUM_BODY_HH #include #include #include "parser/sip_body.h" #include "sockets/url.h" // RFC 3458 6.2 // Message contexts #define MSG_CONTEXT_VOICE "voice-message" #define MSG_CONTEXT_FAX "fax-message" #define MSG_CONTEXT_MULTIMEDIA "multimedia-message" #define MSG_CONTEXT_TEXT "text-message" #define MSG_CONTEXT_NONE "none" using namespace std; /** Message summary counters */ struct t_msg_summary { uint32 newmsgs; uint32 newmsgs_urgent; uint32 oldmsgs; uint32 oldmsgs_urgent; t_msg_summary(); /** * Parse a text representation of a message summary. * @param s [in] The text to parse. * @return false if parsing fails, true if it succeeds. */ bool parse(const string &s); /** Set all counters to zero */ void clear(void); }; typedef string t_msg_context; typedef map::const_iterator t_msg_sum_const_iter; class t_simple_msg_sum_body : public t_sip_body { private: bool msg_waiting; t_url msg_account; map msg_summary; // Returns true if string is a valid message context bool is_context(const string &s); public: t_simple_msg_sum_body(); // Return text encoded body virtual string encode(void) const; // Create a copy of the body virtual t_sip_body *copy(void) const; // Get type of body virtual t_body_type get_type(void) const; virtual t_media get_media(void) const; // Add a message summary void add_msg_summary(const string &context, const t_msg_summary summary); bool get_msg_waiting(void) const; t_url get_msg_account(void) const; // Get the message summary for a particular context // If the context is not present, then false is returned bool get_msg_summary(const string &context, t_msg_summary &summary) const; // Parse a text representation of the body. bool parse(const string &s); }; #endif twinkle-1.10.1/src/parser/000077500000000000000000000000001277565361200153505ustar00rootroot00000000000000twinkle-1.10.1/src/parser/CMakeLists.txt000066400000000000000000000035421277565361200201140ustar00rootroot00000000000000project(libtwinkle-parser) BISON_TARGET(MyParser parser.yxx ${CMAKE_CURRENT_BINARY_DIR}/parser.cxx) FLEX_TARGET(MyScanner scanner.lxx ${CMAKE_CURRENT_BINARY_DIR}/scanner.cxx) ADD_FLEX_BISON_DEPENDENCY(MyScanner MyParser) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) set(LIBTWINKLE_PARSER-SRCS challenge.cpp coding.cpp credentials.cpp definitions.cpp hdr_accept.cpp hdr_accept_encoding.cpp hdr_accept_language.cpp hdr_alert_info.cpp hdr_allow.cpp hdr_allow_events.cpp hdr_auth_info.cpp hdr_authorization.cpp hdr_call_id.cpp hdr_call_info.cpp hdr_contact.cpp hdr_content_disp.cpp hdr_content_encoding.cpp hdr_content_language.cpp hdr_content_length.cpp hdr_content_type.cpp hdr_cseq.cpp hdr_date.cpp hdr_error_info.cpp hdr_event.cpp hdr_expires.cpp hdr_from.cpp hdr_in_reply_to.cpp hdr_max_forwards.cpp hdr_min_expires.cpp hdr_mime_version.cpp hdr_organization.cpp hdr_priority.cpp hdr_privacy.cpp hdr_p_asserted_identity.cpp hdr_p_preferred_identity.cpp hdr_proxy_authenticate.cpp hdr_proxy_authorization.cpp hdr_proxy_require.cpp hdr_rack.cpp hdr_record_route.cpp hdr_refer_sub.cpp hdr_refer_to.cpp hdr_referred_by.cpp hdr_replaces.cpp hdr_reply_to.cpp hdr_require.cpp hdr_request_disposition.cpp hdr_retry_after.cpp hdr_route.cpp hdr_rseq.cpp hdr_server.cpp hdr_service_route.cpp hdr_sip_etag.cpp hdr_sip_if_match.cpp hdr_subject.cpp hdr_subscription_state.cpp hdr_supported.cpp hdr_timestamp.cpp hdr_to.cpp hdr_unsupported.cpp hdr_user_agent.cpp hdr_via.cpp hdr_warning.cpp hdr_www_authenticate.cpp header.cpp identity.cpp media_type.cpp milenage.cpp parameter.cpp parse_ctrl.cpp ${CMAKE_CURRENT_BINARY_DIR}/parser.cxx request.cpp response.cpp rijndael.cpp route.cpp ${CMAKE_CURRENT_BINARY_DIR}/scanner.cxx sip_body.cpp sip_message.cpp ) add_library(libtwinkle-parser OBJECT ${LIBTWINKLE_PARSER-SRCS}) twinkle-1.10.1/src/parser/challenge.cpp000066400000000000000000000074141277565361200200040ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include "challenge.h" #include "definitions.h" #include "log.h" #include "util.h" t_digest_challenge::t_digest_challenge() { stale = false; // The default algorithm is MD5. algorithm = ALG_MD5; } string t_digest_challenge::encode(void) const { string s; if (realm.size() > 0) { if (s.size() > 0) s += ','; s += "realm="; s += '"'; s += realm; s += '"'; } if (domain.size() > 0) { if (s.size() > 0) s += ','; s += "domain="; s += '"'; for (list::const_iterator i = domain.begin(); i != domain.end(); i++) { if (i != domain.begin()) s += ' '; s += i->encode(); } s += '"'; } if (nonce.size() > 0) { if (s.size() > 0) s += ','; s += "nonce="; s += '"'; s += nonce; s += '"'; } if (opaque.size() > 0) { if (s.size() > 0) s += ','; s += "opaque="; s += '"'; s += opaque; s += '"'; } // RFC 2617 3.2.1 // If the stale flag is absent it means stale=false. if (stale) { if (s.size() > 0) s += ','; s += "stale=true"; } if (algorithm.size() > 0) { if (s.size() > 0) s += ','; s += "algorithm="; s += algorithm; } if (qop_options.size() > 0) { if (s.size() > 0) s += ','; s += "qop="; s += '"'; for (list::const_iterator i = qop_options.begin(); i != qop_options.end(); i++) { if (i != qop_options.begin()) s += ','; s += *i; } s += '"'; } for (list::const_iterator i = auth_params.begin(); i != auth_params.end(); i++) { if (s.size() > 0) s += ','; s += i->encode(); } return s; } bool t_digest_challenge::set_attr(const t_parameter &p) { if (p.name == "realm") realm = p.value; else if (p.name == "nonce") nonce = p.value; else if (p.name == "opaque") opaque = p.value; else if (p.name == "algorithm") algorithm = p.value; else if (p.name == "domain") { vector l = split_ws(p.value); for (vector::iterator i = l.begin(); i != l.end(); i++) { t_url u(*i); if (u.is_valid()) { domain.push_back(u); } else { log_file->write_header("t_digest_challenge::set_attr", LOG_SIP, LOG_WARNING); log_file->write_raw("Invalid domain in digest challenge: "); log_file->write_raw(*i); log_file->write_endl(); log_file->write_footer(); } } } else if (p.name == "qop") { vector l = split(p.value, ','); for (vector::iterator i = l.begin(); i != l.end(); i++) { qop_options.push_back(trim(*i)); } } else if (p.name == "stale") { if (cmp_nocase(p.value, "true") == 0) stale = true; else // RFC 2617 3.2.1 // Any value other than false should be interpreted // as false. stale = false; } else auth_params.push_back(p); return true; } string t_challenge::encode(void) const { string s = auth_scheme; s += ' '; if (cmp_nocase(auth_scheme,AUTH_DIGEST) == 0) { s += digest_challenge.encode(); } else { for (list::const_iterator i = auth_params.begin(); i != auth_params.end(); i++) { if (i != auth_params.begin()) s += ','; s += i->encode(); } } return s; } twinkle-1.10.1/src/parser/challenge.h000066400000000000000000000032271277565361200174470ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Challenges are used in Proxy-Authenticate and // WWW-Authenticate headers #ifndef _CHALLENGE_H #define _CHALLENGE_H #include #include #include "parameter.h" #include "sockets/url.h" using namespace std; class t_digest_challenge { public: string realm; list domain; string nonce; string opaque; bool stale; string algorithm; list qop_options; list auth_params; t_digest_challenge(); // Set one of the attributes to a value. The parameter p // indicated wich attribute (p.name) should be set to // which value (p.value). // Returns false if p does not contain a valid attribute // setting. bool set_attr(const t_parameter &p); string encode(void) const; }; class t_challenge { public: string auth_scheme; t_digest_challenge digest_challenge; // auth_params is used when auth_scheme is not Digest. list auth_params; string encode(void) const; }; #endif twinkle-1.10.1/src/parser/coding.cpp000066400000000000000000000017771277565361200173330ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "coding.h" #include "util.h" t_coding::t_coding() { q = 1.0; } t_coding::t_coding(const string &s) { q = 1.0; content_coding = s; } string t_coding::encode(void) const { string s; s = content_coding; if (q != 1.0) { s += ";q="; s += float2str(q, 1); } return s; } twinkle-1.10.1/src/parser/coding.h000066400000000000000000000017411277565361200167670ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Content coding type #ifndef _CODING_H #define _CODING_H #include using namespace std; class t_coding { public: string content_coding; float q; // quality factor; t_coding(); t_coding(const string &s); string encode(void) const; }; #endif twinkle-1.10.1/src/parser/credentials.cpp000066400000000000000000000063071277565361200203570ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "credentials.h" #include "definitions.h" #include "util.h" t_digest_response::t_digest_response() { nonce_count = 0; } string t_digest_response::encode(void) const { string s; if (username.size() > 0) { s += "username="; s += '"'; s += username; s += '"'; } if (realm.size() > 0) { if (s.size() > 0) s += ','; s += "realm="; s += '"'; s += realm; s += '"'; } if (nonce.size() > 0) { if (s.size() > 0) s += ','; s += "nonce="; s += '"'; s += nonce; s += '"'; } if (digest_uri.is_valid()) { if (s.size() > 0) s += ','; s += "uri="; s += '"'; s += digest_uri.encode(); s += '"'; } if (dresponse.size() > 0) { if (s.size() > 0) s += ','; s += "response="; s += '"'; s += dresponse; s += '"'; } if (algorithm.size() > 0) { if (s.size() > 0) s += ','; s += "algorithm="; s += algorithm; } if (cnonce.size() > 0) { if (s.size() > 0) s += ','; s += "cnonce="; s += '"'; s += cnonce; s += '"'; } if (opaque.size() > 0) { if (s.size() > 0) s += ','; s += "opaque="; s += '"'; s += opaque; s += '"'; } if (message_qop.size() > 0) { if (s.size() > 0) s += ','; s += "qop="; s += message_qop; } if (nonce_count > 0) { if (s.size() > 0) s += ','; s += "nc="; s += ulong2str(nonce_count, "%08x"); } for (list::const_iterator i = auth_params.begin(); i != auth_params.end(); i++) { if (s.size() > 0) s += ','; s += i->encode(); } return s; } bool t_digest_response::set_attr(const t_parameter &p) { if (p.name == "username") username = p.value; else if (p.name == "realm") realm = p.value; else if (p.name == "nonce") nonce = p.value; else if (p.name == "digest_uri") { digest_uri.set_url(p.value); if (!digest_uri.is_valid()) return false; } else if (p.name == "response") dresponse = p.value; else if (p.name == "cnonce") cnonce = p.value; else if (p.name == "opaque") opaque = p.value; else if (p.name == "algorithm") algorithm = p.value; else if (p.name == "qop") message_qop = p.value; else if (p.name == "nc") nonce_count = hex2int(p.value); else auth_params.push_back(p); return true; } string t_credentials::encode(void) const { string s = auth_scheme; s += ' '; if (cmp_nocase(auth_scheme,AUTH_DIGEST) == 0) { s += digest_response.encode(); } else { for (list::const_iterator i = auth_params.begin(); i != auth_params.end(); i++) { if (i != auth_params.begin()) s += ','; s += i->encode(); } } return s; } twinkle-1.10.1/src/parser/credentials.h000066400000000000000000000033331277565361200200200ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Credentials are used in Proxy-Authorization and // Authorization headers #ifndef _CREDENTIALS_H #define _CREDENTIALS_H #include #include #include "parameter.h" #include "sockets/url.h" using namespace std; class t_digest_response { public: string username; string realm; string nonce; t_url digest_uri; string dresponse; string algorithm; string cnonce; string opaque; string message_qop; unsigned long nonce_count; list auth_params; t_digest_response(); // Set one of the attributes to a value. The parameter p // indicated wich attribute (p.name) should be set to // which value (p.value). // Returns false if p does not contain a valid attribute // setting. bool set_attr(const t_parameter &p); string encode(void) const; }; class t_credentials { public: string auth_scheme; t_digest_response digest_response; // auth_params is used when auth_scheme is not Digest. list auth_params; string encode(void) const; }; #endif twinkle-1.10.1/src/parser/definitions.cpp000066400000000000000000000035451277565361200203760ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include "definitions.h" #include "parse_ctrl.h" string method2str(const t_method &m, const string &unknown) { switch (m) { case INVITE: return "INVITE"; case ACK: return "ACK"; case OPTIONS: return "OPTIONS"; case BYE: return "BYE"; case CANCEL: return "CANCEL"; case REGISTER: return "REGISTER"; case PRACK: return "PRACK"; case SUBSCRIBE: return "SUBSCRIBE"; case NOTIFY: return "NOTIFY"; case REFER: return "REFER"; case INFO: return "INFO"; case MESSAGE: return "MESSAGE"; case PUBLISH: return "PUBLISH"; case METHOD_UNKNOWN: return unknown; default: assert(false); } return unknown; } t_method str2method(const string &s) { if (s == "INVITE") return INVITE; if (s == "ACK") return ACK; if (s == "OPTIONS") return OPTIONS; if (s == "BYE") return BYE; if (s == "CANCEL") return CANCEL; if (s == "REGISTER") return REGISTER; if (s == "PRACK") return PRACK; if (s == "SUBSCRIBE") return SUBSCRIBE; if (s == "NOTIFY") return NOTIFY; if (s == "REFER") return REFER; if (s == "INFO") return INFO; if (s == "MESSAGE") return MESSAGE; if (s == "PUBLISH") return PUBLISH; return METHOD_UNKNOWN; } twinkle-1.10.1/src/parser/definitions.h000066400000000000000000000034651277565361200200440ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _DEFINITIONS_H #define _DEFINITIONS_H #include #include "protocol.h" #include "sockets/url.h" using namespace std; #define SIP_VERSION "2.0" // RFC 3261 #ifndef RFC3261_COOKIE #define RFC3261_COOKIE "z9hG4bK" #endif // Authentication schemes #define AUTH_DIGEST "Digest" // Authentication algorithms #define ALG_MD5 "MD5" #define ALG_AKAV1_MD5 "AKAV1-MD5" #define ALG_MD5_SESS "MD5-sess" // Authentication QOP #define QOP_AUTH "auth" #define QOP_AUTH_INT "auth-int" /** SIP request methods. */ enum t_method { INVITE, ACK, OPTIONS, BYE, CANCEL, REGISTER, PRACK, SUBSCRIBE, NOTIFY, REFER, INFO, MESSAGE, PUBLISH, METHOD_UNKNOWN }; /** * Convert a method to a string. * @param m The method. * @param unknown Method name if m is @ref METHOD_UNKNOWN. * @return The name of the method. */ string method2str(const t_method &m, const string &unknown = ""); /** * Convert a string to a method. * @param s The string. * @return The method having s as name. If s is an unknown name, * then @ref METHOD_UNKNOWN is returned. */ t_method str2method(const string &s); #endif twinkle-1.10.1/src/parser/hdr_accept.cpp000066400000000000000000000024071277565361200201530ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_accept.h" #include "definitions.h" t_hdr_accept::t_hdr_accept() : t_header("Accept") {}; void t_hdr_accept::add_media(const t_media &media) { populated = true; media_list.push_back(media); } void t_hdr_accept::set_empty(void) { populated = true; media_list.clear(); } string t_hdr_accept::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = media_list.begin(); i != media_list.end(); i++) { if (i != media_list.begin()) s += ","; s += i->encode(); } return s; } twinkle-1.10.1/src/parser/hdr_accept.h000066400000000000000000000023261277565361200176200ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Accept header #ifndef _HDR_ACCEPT_H #define _HDR_ACCEPT_H #include #include "header.h" #include "media_type.h" class t_hdr_accept : public t_header { public: list media_list; // list of accepted media t_hdr_accept(); // Add a media to the list of accepted media void add_media(const t_media &media); // Clear the list of features, but make the header 'populated'. // An empty header will be in the message. void set_empty(void); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_accept_encoding.cpp000066400000000000000000000023631277565361200220220ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_accept_encoding.h" #include "definitions.h" t_hdr_accept_encoding::t_hdr_accept_encoding() : t_header("Accept-Encoding") {}; void t_hdr_accept_encoding::add_coding(const t_coding &coding) { populated = true; coding_list.push_back(coding); } string t_hdr_accept_encoding::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = coding_list.begin(); i != coding_list.end(); i++) { if (i != coding_list.begin()) s += ","; s += i->encode(); } return s; } twinkle-1.10.1/src/parser/hdr_accept_encoding.h000066400000000000000000000022531277565361200214650ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Accept-Encoding header #ifndef _HDR_ACCEPT_ENCODING_H #define _HDR_ACCEPT_ENCODING_H #include #include #include "coding.h" #include "header.h" using namespace std; class t_hdr_accept_encoding : public t_header { public: list coding_list; // list of content codings; t_hdr_accept_encoding(); // Add a coding to the list of content codings void add_coding(const t_coding &coding); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_accept_language.cpp000066400000000000000000000030461277565361200220160ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_accept_language.h" #include "util.h" t_language::t_language() { language = "en"; q = 1.0; } t_language::t_language(const string &l) { language = l; q = 1.0; } string t_language::encode(void) const { string s; s = language; if (q != 1.0) { s += ";q="; s += float2str(q, 1); } return s; } t_hdr_accept_language::t_hdr_accept_language() : t_header("Accept-Language") {}; void t_hdr_accept_language::add_language(const t_language &language) { populated = true; language_list.push_back(language); } string t_hdr_accept_language::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = language_list.begin(); i != language_list.end(); i++) { if (i != language_list.begin()) s += ","; s += i->encode(); } return s; } twinkle-1.10.1/src/parser/hdr_accept_language.h000066400000000000000000000024731277565361200214660ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Accept_Language header #ifndef _HDR_ACCEPT_LANGUAGE_H #define _HDR_ACCEPT_LANGUAGE_H #include #include #include "header.h" using namespace std; class t_language { public: string language; float q; // quality factor t_language(); t_language(const string &l); string encode(void) const; }; class t_hdr_accept_language : public t_header { public: list language_list; // list of accepted languages t_hdr_accept_language(); // Add a language to the list of accepted media void add_language(const t_language &language); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_alert_info.cpp000066400000000000000000000027141277565361200210370ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_alert_info.h" void t_alert_param::add_param(const t_parameter &p) { parameter_list.push_back(p); } string t_alert_param::encode(void) const { string s; s = '<' + uri.encode() + '>'; s += param_list2str(parameter_list); return s; } t_hdr_alert_info::t_hdr_alert_info() : t_header("Alert-Info") {}; void t_hdr_alert_info::add_param(const t_alert_param &p) { populated = true; alert_param_list.push_back(p); } string t_hdr_alert_info::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = alert_param_list.begin(); i != alert_param_list.end(); i++) { if (i != alert_param_list.begin()) s += ", "; s += i->encode(); } return s; } twinkle-1.10.1/src/parser/hdr_alert_info.h000066400000000000000000000024631277565361200205050ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Alert-Info header #ifndef _HDR_ALERT_INFO_H #define _HDR_ALERT_INFO_H #include #include #include "header.h" #include "parameter.h" #include "sockets/url.h" using namespace std; class t_alert_param { public: t_url uri; list parameter_list; void add_param(const t_parameter &p); string encode(void) const; }; class t_hdr_alert_info : public t_header { public: list alert_param_list; t_hdr_alert_info(); // Add a paramter to the list of alert parameters void add_param(const t_alert_param &p); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_allow.cpp000066400000000000000000000035471277565361200200400ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include "definitions.h" #include "hdr_allow.h" using namespace std; t_hdr_allow::t_hdr_allow() : t_header("Allow") {} void t_hdr_allow::add_method(const t_method &m, const string &unknown) { populated = true; if (m != METHOD_UNKNOWN) { method_list.push_back(m); } else { unknown_methods.push_back(unknown); } } void t_hdr_allow::add_method(const string &s) { populated = true; t_method m = str2method(s); if (m != METHOD_UNKNOWN) { method_list.push_back(m); } else { unknown_methods.push_back(s); } } bool t_hdr_allow::contains_method(const t_method &m) const { return (find(method_list.begin(), method_list.end(), m) != method_list.end()); } string t_hdr_allow::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = method_list.begin(); i != method_list.end(); i++) { if (i != method_list.begin()) s += ","; s += method2str(*i); } for (list::const_iterator i = unknown_methods.begin(); i != unknown_methods.end(); i++) { if (i != unknown_methods.begin() || method_list.size() != 0) { s += ","; } s += *i; } return s; } twinkle-1.10.1/src/parser/hdr_allow.h000066400000000000000000000023511277565361200174750ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Allow header #ifndef _HDR_ALLOW_H #define _HDR_ALLOW_H #include #include #include "header.h" #include "definitions.h" using namespace std; class t_hdr_allow : public t_header { public: list method_list; // Unknown methods are represented as strings list unknown_methods; t_hdr_allow(); void add_method(const t_method &m, const string &unknown = ""); void add_method(const string &s); bool contains_method(const t_method &m) const; string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_allow_events.cpp000066400000000000000000000023211277565361200214110ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_allow_events.h" #include "parse_ctrl.h" t_hdr_allow_events::t_hdr_allow_events() : t_header("Allow-Events", "u") {} void t_hdr_allow_events::add_event_type(const string &t) { populated = true; event_types.push_back(t); } string t_hdr_allow_events::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = event_types.begin(); i != event_types.end(); i++) { if (i != event_types.begin()) s += ","; s += *i; } return s; } twinkle-1.10.1/src/parser/hdr_allow_events.h000066400000000000000000000020761277565361200210650ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Allow-Events header // RFC 3265 #ifndef _HDR_ALLOW_EVENTS #define _HDR_ALLOW_EVENTS #include #include #include "header.h" using namespace std; class t_hdr_allow_events : public t_header { public: list event_types; t_hdr_allow_events(); void add_event_type(const string &t); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_auth_info.cpp000066400000000000000000000041631277565361200206710ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_auth_info.h" #include "definitions.h" #include "util.h" t_hdr_auth_info::t_hdr_auth_info() : t_header("Authentication-Info") { nonce_count = 0; } void t_hdr_auth_info::set_next_nonce(const string &nn) { populated = true; next_nonce = nn; } void t_hdr_auth_info::set_message_qop(const string &mq) { populated = true; message_qop = mq; } void t_hdr_auth_info::set_response_auth(const string &ra) { populated = true; response_auth = ra; } void t_hdr_auth_info::set_cnonce(const string &cn) { populated = true; cnonce = cn; } void t_hdr_auth_info::set_nonce_count(const unsigned long &nc) { populated = true; nonce_count = nc; } string t_hdr_auth_info::encode_value(void) const { string s; bool add_comma = false; if (!populated) return s; if (next_nonce.size() > 0) { s += "nextnonce="; s += '"'; s += next_nonce; s += '"'; add_comma = true; } if (message_qop.size() > 0) { if (add_comma) s += ','; s += "qop="; s += message_qop; add_comma = true; } if (response_auth.size() > 0) { if (add_comma) s += ','; s += "rspauth="; s += '"'; s += response_auth; s += '"'; add_comma = true; } if (cnonce.size() > 0) { if (add_comma) s += ','; s += "cnonce="; s += '"'; s += cnonce; s += '"'; add_comma = true; } if (nonce_count > 0) { if (add_comma) s += ','; s += "nc="; s += ulong2str(nonce_count, "%08x"); add_comma = true; } return s; } twinkle-1.10.1/src/parser/hdr_auth_info.h000066400000000000000000000024441277565361200203360ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Authentication-Info header #ifndef _HDR_AUTH_INFO_H #define _HDR_AUTH_INFO_H #include #include "header.h" using namespace std; class t_hdr_auth_info : public t_header { public: string next_nonce; string message_qop; string response_auth; string cnonce; unsigned long nonce_count; t_hdr_auth_info(); void set_next_nonce(const string &nn); void set_message_qop(const string &mq); void set_response_auth(const string &ra); void set_cnonce(const string &cn); void set_nonce_count(const unsigned long &nc); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_authorization.cpp000066400000000000000000000044141277565361200216140ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_authorization.h" #include "definitions.h" t_hdr_authorization::t_hdr_authorization() : t_header("Authorization") {} void t_hdr_authorization::add_credentials(const t_credentials &c) { populated = true; credentials_list.push_back(c); } string t_hdr_authorization::encode(void) const { string s; if (!populated) return s; // RFC 3261 20.7 // Each authorization should appear as a separate header for (list::const_iterator i = credentials_list.begin(); i != credentials_list.end(); i++) { s += header_name; s += ": "; s += i->encode(); s += CRLF; } return s; } string t_hdr_authorization::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = credentials_list.begin(); i != credentials_list.end(); i++) { if (i != credentials_list.begin()) s += ", "; s += i->encode(); } return s; } bool t_hdr_authorization::contains(const string &realm, const t_url &uri) const { for (list::const_iterator i = credentials_list.begin(); i != credentials_list.end(); i++) { if (i->digest_response.realm == realm && i->digest_response.digest_uri == uri) { return true; } } return false; } void t_hdr_authorization::remove_credentials(const string &realm, const t_url &uri) { for (list::iterator i = credentials_list.begin(); i != credentials_list.end(); i++) { if (i->digest_response.realm == realm && i->digest_response.digest_uri == uri) { credentials_list.erase(i); return; } } } twinkle-1.10.1/src/parser/hdr_authorization.h000066400000000000000000000026411277565361200212610ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Authorization header #ifndef _HDR_AUTHORIZATION_H #define _HDR_AUTHORIZATION_H #include #include #include "credentials.h" #include "header.h" #include "parameter.h" #include "sockets/url.h" using namespace std; class t_hdr_authorization : public t_header { public: list credentials_list; t_hdr_authorization(); void add_credentials(const t_credentials &c); string encode(void) const; string encode_value(void) const; // Return true if the header contains credentials for a realm/dest bool contains(const string &realm, const t_url &uri) const; // Remove credentials for a realm/dest void remove_credentials(const string &realm, const t_url &uri); }; #endif twinkle-1.10.1/src/parser/hdr_call_id.cpp000066400000000000000000000020361277565361200203010ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_call_id.h" #include "definitions.h" #include "parse_ctrl.h" t_hdr_call_id::t_hdr_call_id() : t_header("Call-ID", "i") {}; void t_hdr_call_id::set_call_id(const string &id) { populated = true; call_id = id; } string t_hdr_call_id::encode_value(void) const { if (!populated) return ""; return call_id; } twinkle-1.10.1/src/parser/hdr_call_id.h000066400000000000000000000020001277565361200177350ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Call-ID header #ifndef _HDR_CALL_ID_H #define _HDR_CALL_ID_H #include #include "header.h" using namespace std; class t_hdr_call_id : public t_header { public: string call_id; t_hdr_call_id(); void set_call_id(const string &id); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_call_info.cpp000066400000000000000000000026771277565361200206530ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_call_info.h" void t_info_param::add_param(const t_parameter &p) { parameter_list.push_back(p); } string t_info_param::encode(void) const { string s; s = '<' + uri.encode() + '>'; s += param_list2str(parameter_list); return s; } t_hdr_call_info::t_hdr_call_info() : t_header("Call-Info") {}; void t_hdr_call_info::add_param(const t_info_param &p) { populated = true; info_param_list.push_back(p); } string t_hdr_call_info::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = info_param_list.begin(); i != info_param_list.end(); i++) { if (i != info_param_list.begin()) s += ", "; s += i->encode(); } return s; } twinkle-1.10.1/src/parser/hdr_call_info.h000066400000000000000000000024521277565361200203070ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Call-Info header #ifndef _HDR_CALL_INFO_H #define _HDR_CALL_INFO_H #include #include #include "header.h" #include "parameter.h" #include "sockets/url.h" using namespace std; class t_info_param { public: t_url uri; list parameter_list; void add_param(const t_parameter &p); string encode(void) const; }; class t_hdr_call_info : public t_header { public: list info_param_list; t_hdr_call_info(); // Add a paramter to the list of alert parameters void add_param(const t_info_param &p); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_contact.cpp000066400000000000000000000072641277565361200203550ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_contact.h" #include "parse_ctrl.h" #include "util.h" t_contact_param::t_contact_param() { qvalue = 1.0; qvalue_present = false; expires = 0; expires_present = false; } void t_contact_param::add_extension(const t_parameter &p) { extensions.push_back(p); } string t_contact_param::encode(void) const { string s; if (display.size() > 0) { s += '"'; s += escape(display, '"'); s += '"'; s += ' '; } s += '<'; s += uri.encode(); s += '>'; if (qvalue_present) { s += ";q="; s += float2str(qvalue, 3); } if (expires_present) s += ulong2str(expires, ";expires=%u"); s += param_list2str(extensions); return s; } bool t_contact_param::operator<(const t_contact_param &c) const { return (qvalue > c.qvalue); } t_hdr_contact::t_hdr_contact() : t_header("Contact", "m") { any_flag = false; } void t_hdr_contact::add_contact(const t_contact_param &contact) { populated = true; contact_list.push_back(contact); } void t_hdr_contact::add_contacts(const list &l) { populated = true; for (list::const_iterator i = l.begin(); i != l.end(); i++) { contact_list.push_back(*i); } } void t_hdr_contact::set_contacts(const list &l) { populated = true; contact_list = l; } void t_hdr_contact::set_contacts(const list &l) { t_contact_param c; float q = 0.9; populated = true; contact_list.clear(); for (list::const_iterator i = l.begin(); i != l.end(); i++) { c.uri = *i; c.set_qvalue(q); contact_list.push_back(c); q = q - 0.1; if (q < 0.1) q = 0.1; } } void t_hdr_contact::set_contacts(const list &l) { t_contact_param c; float q = 0.9; populated = true; contact_list.clear(); for (list::const_iterator i = l.begin(); i != l.end(); i++) { c.uri = i->url; c.display = i->display; c.set_qvalue(q); contact_list.push_back(c); q = q - 0.1; if (q < 0.1) q = 0.1; } } void t_hdr_contact::set_any(void) { populated = true; any_flag = true; contact_list.clear(); } t_contact_param *t_hdr_contact::find_contact(const t_url &u) { for (list::iterator i = contact_list.begin(); i != contact_list.end(); i++) { if (u.sip_match(i->uri)) return &(*i); } return NULL; } bool t_contact_param::is_expires_present(void) const { return expires_present; } unsigned long t_contact_param::get_expires(void) const { return expires; } void t_contact_param::set_expires(unsigned long e) { expires_present = true; expires = e; } float t_contact_param::get_qvalue(void) const { return qvalue; } void t_contact_param::set_qvalue(float q) { qvalue_present = true; qvalue = q; } string t_hdr_contact::encode_value(void) const { string s; if (!populated) return s; if (any_flag) { s += '*'; return s; } for (list::const_iterator i = contact_list.begin(); i != contact_list.end(); i++) { if (i != contact_list.begin()) s += ", "; s += i->encode(); } return s; } twinkle-1.10.1/src/parser/hdr_contact.h000066400000000000000000000055501277565361200200160ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Contact header #ifndef _HDR_CONTACT #define _HDR_CONTACT #include #include #include "header.h" #include "parameter.h" #include "sockets/url.h" using namespace std; class t_contact_param { private: bool expires_present; unsigned long expires; bool qvalue_present; float qvalue; public: string display; // display name t_url uri; list extensions; t_contact_param(); void add_extension(const t_parameter &p); string encode(void) const; bool is_expires_present(void) const; unsigned long get_expires(void) const; void set_expires(unsigned long e); float get_qvalue(void) const; void set_qvalue(float q); // Compare contacts on q-value. // The contacts with the highest q-value comes first in the order bool operator<(const t_contact_param &c) const; }; class t_hdr_contact : public t_header { public: bool any_flag; // true if Contact: * list contact_list; t_hdr_contact(); void add_contact(const t_contact_param &contact); void add_contacts(const list &l); void set_contacts(const list &l); /** * Set the contact list to a sequence of URI's with display names. * The URI's are give a descending q-value starting at 0.9 * Each subsequent URI gets a q-value 0.1 less than the previous * URI. If more than 9 URI's are passed then the tail of URI's all * get a q-value of 0.1. * * @param l [in] The list of URI's to be put in the contact list. */ void set_contacts(const list &l); /** * Set the contact list to a sequence of URI's with display names. * The URI's are give a descending q-value starting at 0.9 * Each subsequent URI gets a q-value 0.1 less than the previous * URI. If more than 9 URI's are passed then the tail of URI's all * get a q-value of 0.1. * * @param l [in] The list of URI's to be put in the contact list. */ void set_contacts(const list &l); // Set contact to any, eg. Contact: * void set_any(void); // Find contact with uri u. If no contact is found, then // NULL is returned. t_contact_param *find_contact(const t_url &u); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_content_disp.cpp000066400000000000000000000027241277565361200214070ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_content_disp.h" t_hdr_content_disp::t_hdr_content_disp() : t_header("Content-Disposition") {}; void t_hdr_content_disp::set_type(const string &t) { populated = true; type = t; } void t_hdr_content_disp::set_filename(const string &name) { populated = true; filename = name; } void t_hdr_content_disp::add_param(const t_parameter &p) { populated = true; params.push_back(p); } void t_hdr_content_disp::set_params(const list &l) { populated = true; params = l; } string t_hdr_content_disp::encode_value(void) const { string s; if (!populated) return s; s = type; if (!filename.empty()) { s += ";filename=\""; s += filename; s += "\""; } s += param_list2str(params); return s; } twinkle-1.10.1/src/parser/hdr_content_disp.h000066400000000000000000000025011277565361200210450ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Content-Disposition header #ifndef _HDR_CONTENT_DISP #define _HDR_CONTENT_DISP #include #include #include "header.h" #include "parameter.h" using namespace std; //@{ /** @name Disposition types */ #define DISPOSITION_ATTACHMENT "attachment" //@} class t_hdr_content_disp : public t_header { public: string type; string filename; list params; t_hdr_content_disp(); void set_type(const string &t); void set_filename(const string &name); void add_param(const t_parameter &p); void set_params(const list &l); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_content_encoding.cpp000066400000000000000000000024271277565361200222360ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_content_encoding.h" #include "definitions.h" #include "parse_ctrl.h" t_hdr_content_encoding::t_hdr_content_encoding() : t_header("Content-Encoding", "e") {}; void t_hdr_content_encoding::add_coding(const t_coding &coding) { populated = true; coding_list.push_back(coding); } string t_hdr_content_encoding::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = coding_list.begin(); i != coding_list.end(); i++) { if (i != coding_list.begin()) s += ", "; s += i->encode(); } return s; } twinkle-1.10.1/src/parser/hdr_content_encoding.h000066400000000000000000000022601277565361200216760ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Content-Encoding header #ifndef _HDR_CONTENT_ENCODING_H #define _HDR_CONTENT_ENCODING_H #include #include #include "coding.h" #include "header.h" using namespace std; class t_hdr_content_encoding : public t_header { public: list coding_list; // list of content codings; t_hdr_content_encoding(); // Add a coding to the list of content codings void add_coding(const t_coding &coding); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_content_language.cpp000066400000000000000000000024361277565361200222330ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_content_language.h" #include "util.h" t_hdr_content_language::t_hdr_content_language() : t_header("Content-Language") {}; void t_hdr_content_language::add_language(const t_language &language) { populated = true; language_list.push_back(language); } string t_hdr_content_language::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = language_list.begin(); i != language_list.end(); i++) { if (i != language_list.begin()) s += ", "; s += i->encode(); } return s; } twinkle-1.10.1/src/parser/hdr_content_language.h000066400000000000000000000022741277565361200217000ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Content_Language header #ifndef _HDR_CONTENT_LANGUAGE_H #define _HDR_CONTENT_LANGUAGE_H #include #include #include "header.h" #include "hdr_accept_language.h" using namespace std; class t_hdr_content_language : public t_header { public: list language_list; // list of languages t_hdr_content_language(); // Add a language to the list of languages void add_language(const t_language &language); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_content_length.cpp000066400000000000000000000022011277565361200217170ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_content_length.h" #include "parse_ctrl.h" #include "util.h" t_hdr_content_length::t_hdr_content_length() : t_header("Content-Length", "l") { length = 0; } void t_hdr_content_length::set_length(unsigned long l) { populated = true; length = l; } string t_hdr_content_length::encode_value(void) const { string s; if (!populated) return s; s = ulong2str(length); return s; } twinkle-1.10.1/src/parser/hdr_content_length.h000066400000000000000000000020441277565361200213710ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Content-Length header #ifndef _HDR_CONTENT_LENGTH #define _HDR_CONTENT_LENGTH #include #include "header.h" using namespace std; class t_hdr_content_length : public t_header { public: unsigned long length; t_hdr_content_length(); void set_length(unsigned long l); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_content_type.cpp000066400000000000000000000021221277565361200214210ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_content_type.h" #include "definitions.h" #include "parse_ctrl.h" t_hdr_content_type::t_hdr_content_type() : t_header("Content-Type", "c") {}; void t_hdr_content_type::set_media(const t_media &m) { populated = true; media = m; } string t_hdr_content_type::encode_value(void) const { string s; if (!populated) return s; s = media.encode(); return s; } twinkle-1.10.1/src/parser/hdr_content_type.h000066400000000000000000000020061277565361200210670ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Content-Type header #ifndef _HDR_CONTENT_TYPE_H #define _HDR_CONTENT_TYPE_H #include "header.h" #include "media_type.h" class t_hdr_content_type : public t_header { public: t_media media; t_hdr_content_type(); void set_media(const t_media &m); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_cseq.cpp000066400000000000000000000031241277565361200176440ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_cseq.h" #include "util.h" t_hdr_cseq::t_hdr_cseq() : t_header("CSeq") { seqnr = 0; method = INVITE; } void t_hdr_cseq::set_seqnr(unsigned long l) { populated = true; seqnr = l; } void t_hdr_cseq::set_method(t_method m, const string &unknown) { populated = true; method = m; unknown_method = unknown; } void t_hdr_cseq::set_method(const string &s) { populated = true; method = str2method(s); if (method == METHOD_UNKNOWN) { unknown_method = s; } } string t_hdr_cseq::encode_value(void) const { string s; if (!populated) return s; s = ulong2str(seqnr) + ' '; s += method2str(method, unknown_method); return s; } bool t_hdr_cseq::operator==(const t_hdr_cseq &h) const { if (method != METHOD_UNKNOWN) { return (seqnr == h.seqnr && method == h.method); } return (seqnr == h.seqnr && unknown_method == h.unknown_method); } twinkle-1.10.1/src/parser/hdr_cseq.h000066400000000000000000000023331277565361200173120ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // CSeq header #ifndef _HDR_CSEQ #define _HDR_CSEQ #include #include "header.h" #include "definitions.h" using namespace std; class t_hdr_cseq : public t_header { public: unsigned long seqnr; t_method method; string unknown_method; // set if method is UNKNOWN t_hdr_cseq(); void set_seqnr(unsigned long l); void set_method(t_method m, const string &unknown = ""); void set_method(const string &s); string encode_value(void) const; bool operator==(const t_hdr_cseq &h) const; }; #endif twinkle-1.10.1/src/parser/hdr_date.cpp000066400000000000000000000030531277565361200176270ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // NOTE: the date functions are not thread safe #include #include "hdr_date.h" #include "definitions.h" #include "util.h" t_hdr_date::t_hdr_date() : t_header("Date") {} void t_hdr_date::set_date_gm(struct tm *tm) { populated = true; date = timegm(tm); } void t_hdr_date::set_now(void) { struct timeval t; populated = true; gettimeofday(&t, NULL); date = t.tv_sec; } string t_hdr_date::encode_value(void) const { string s; struct tm tm; if (!populated) return s; gmtime_r(&date, &tm); s = weekday2str(tm.tm_wday); s += ", "; s += int2str(tm.tm_mday, "%02d"); s += ' '; s += month2str(tm.tm_mon); s += ' '; s += int2str(tm.tm_year + 1900, "%04d"); s += ' '; s += int2str(tm.tm_hour, "%02d"); s += ':'; s += int2str(tm.tm_min, "%02d"); s += ':'; s += int2str(tm.tm_sec, "%02d"); s += " GMT"; return s; } twinkle-1.10.1/src/parser/hdr_date.h000066400000000000000000000021161277565361200172730ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Date header #ifndef _HDR_DATE_H #define _HDR_DATE_H #include #include #include "header.h" class t_hdr_date : public t_header { public: time_t date; t_hdr_date(); void set_date_gm(struct tm *tm); // set date, tm is GMT void set_now(void); // Set date/time to current date/time string encode_value(void) const; }; using namespace std; #endif twinkle-1.10.1/src/parser/hdr_error_info.cpp000066400000000000000000000027141277565361200210610ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_error_info.h" void t_error_param::add_param(const t_parameter &p) { parameter_list.push_back(p); } string t_error_param::encode(void) const { string s; s = '<' + uri.encode() + '>'; s += param_list2str(parameter_list); return s; } t_hdr_error_info::t_hdr_error_info() : t_header("Error-Info") {}; void t_hdr_error_info::add_param(const t_error_param &p) { populated = true; error_param_list.push_back(p); } string t_hdr_error_info::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = error_param_list.begin(); i != error_param_list.end(); i++) { if (i != error_param_list.begin()) s += ", "; s += i->encode(); } return s; } twinkle-1.10.1/src/parser/hdr_error_info.h000066400000000000000000000024631277565361200205270ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Error-Info header #ifndef _HDR_ERROR_INFO_H #define _HDR_ERROR_INFO_H #include #include #include "header.h" #include "parameter.h" #include "sockets/url.h" using namespace std; class t_error_param { public: t_url uri; list parameter_list; void add_param(const t_parameter &p); string encode(void) const; }; class t_hdr_error_info : public t_header { public: list error_param_list; t_hdr_error_info(); // Add a paramter to the list of error parameters void add_param(const t_error_param &p); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_event.cpp000066400000000000000000000024451277565361200200370ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_event.h" #include "parse_ctrl.h" t_hdr_event::t_hdr_event() : t_header("Event", "o") {} void t_hdr_event::set_event_type(const string &t) { populated = true; event_type = t; } void t_hdr_event::set_id(const string &s) { populated = true; id = s; } void t_hdr_event::add_event_param(const t_parameter &p) { populated = true; event_params.push_back(p); } string t_hdr_event::encode_value(void) const { string s; if (!populated) return s; s += event_type; if (id.size() > 0) { s += ";id="; s += id; } s += param_list2str(event_params); return s; } twinkle-1.10.1/src/parser/hdr_event.h000066400000000000000000000026541277565361200175060ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Event header // RFC 3265 #ifndef _HDR_EVENT #define _HDR_EVENT #include #include #include "header.h" #include "parameter.h" #define SIP_EVENT_REFER "refer" // RFC 3515 #define SIP_EVENT_MSG_SUMMARY "message-summary" // RFC 3842 #define SIP_EVENT_PRESENCE "presence" // RFC 3856 using namespace std; class t_hdr_event : public t_header { public: // The event_type attribute contains the event-template as well // if present, e.g. event.template string event_type; string id; list event_params; t_hdr_event(); void set_event_type(const string &t); void set_id(const string &s); void add_event_param(const t_parameter &p); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_expires.cpp000066400000000000000000000020371277565361200203720ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_expires.h" #include "util.h" t_hdr_expires::t_hdr_expires() : t_header("Expires") { time = 0; } void t_hdr_expires::set_time(unsigned long t) { populated = true; time = t; } string t_hdr_expires::encode_value(void) const { if (!populated) return ""; return ulong2str(time); } twinkle-1.10.1/src/parser/hdr_expires.h000066400000000000000000000020451277565361200200360ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Expires header #ifndef _HDR_EXPIRES_LENGTH #define _HDR_EXPIRES_LENGTH #include #include "header.h" using namespace std; class t_hdr_expires : public t_header { public: unsigned long time; // expiry time in seconds t_hdr_expires(); void set_time(unsigned long t); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_from.cpp000066400000000000000000000034731277565361200176630ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_from.h" #include "definitions.h" #include "parse_ctrl.h" #include "util.h" t_hdr_from::t_hdr_from() : t_header("From", "f") {} void t_hdr_from::set_display(const string &d) { populated = true; display = d; } void t_hdr_from::set_uri(const string &u) { populated = true; uri.set_url(u); } void t_hdr_from::set_uri(const t_url &u) { populated = true; uri = u; } void t_hdr_from::set_tag(const string &t) { populated = true; tag = t; } void t_hdr_from::set_params(const list &l) { populated = true; params = l; } void t_hdr_from::add_param(const t_parameter &p) { populated = true; params.push_back(p); } string t_hdr_from::encode_value(void) const { string s; if (!populated) return s; if (display.size() > 0) { s += '"'; s += escape(display, '"'); s += '"'; s += ' '; } s += '<'; s += uri.encode(); s += '>'; if (tag != "") { s += ";tag="; s += tag; } s += param_list2str(params); return s; } string t_hdr_from::get_display_presentation(void) const { if (display_override.empty()) { return display; } else { return display_override; } } twinkle-1.10.1/src/parser/hdr_from.h000066400000000000000000000032001277565361200173140ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // From header #ifndef _H_HDR_FROM #define _H_HDR_FROM #include #include "header.h" #include "parameter.h" #include "sockets/url.h" using namespace std; class t_hdr_from : public t_header { public: string display; // display name // The display_override may be set by the UA to display another // name to the user, then the display name received in the // signalling, e.g. a lookup from an address book. This value // does NOT appear in the SIP message. string display_override; t_url uri; string tag; list params; t_hdr_from(); void set_display(const string &d); void set_uri(const string &u); void set_uri(const t_url &u); void set_tag(const string &t); void set_params(const list &l); void add_param(const t_parameter &p); string encode_value(void) const; // Get the display name to show to the user. string get_display_presentation(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_in_reply_to.cpp000066400000000000000000000022741277565361200212410ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_in_reply_to.h" #include "definitions.h" t_hdr_in_reply_to::t_hdr_in_reply_to() : t_header("In-Reply-To") {}; void t_hdr_in_reply_to::add_call_id(const string &id) { populated = true; call_ids.push_back(id); } string t_hdr_in_reply_to::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = call_ids.begin(); i != call_ids.end(); i++) { if (i != call_ids.begin()) s += ", "; s += *i; } return s; } twinkle-1.10.1/src/parser/hdr_in_reply_to.h000066400000000000000000000020531277565361200207010ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // In-Reply-To header #ifndef _H_HDR_IN_REPLY_TO #define _H_HDR_IN_REPLY_TO #include #include #include "header.h" using namespace std; class t_hdr_in_reply_to : public t_header { public: list call_ids; t_hdr_in_reply_to(); void add_call_id(const string &id); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_max_forwards.cpp000066400000000000000000000021211277565361200214010ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_max_forwards.h" #include "util.h" t_hdr_max_forwards::t_hdr_max_forwards() : t_header("Max-Forwards") { max_forwards = 0; } void t_hdr_max_forwards::set_max_forwards(int m) { populated = true; max_forwards = m; } string t_hdr_max_forwards::encode_value(void) const { if (!populated) return ""; return int2str(max_forwards); } twinkle-1.10.1/src/parser/hdr_max_forwards.h000066400000000000000000000020401277565361200210460ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Max-Forwards header #ifndef _HDR_MAX_FORWARDS_LENGTH #define _HDR_MAX_FORWARDS_LENGTH #include #include "header.h" using namespace std; class t_hdr_max_forwards : public t_header { public: int max_forwards; t_hdr_max_forwards(); void set_max_forwards(int m); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_mime_version.cpp000066400000000000000000000020361277565361200214060ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_mime_version.h" t_hdr_mime_version::t_hdr_mime_version() : t_header("MIME-Version") {}; void t_hdr_mime_version::set_version(const string &v) { populated = true; version = v; } string t_hdr_mime_version::encode_value(void) const { if (!populated) return ""; return version; } twinkle-1.10.1/src/parser/hdr_mime_version.h000066400000000000000000000020271277565361200210530ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // MIME-Version header #ifndef _H_HDR_MIME_VERSION #define _H_HDR_MIME_VERSION #include #include "header.h" using namespace std; class t_hdr_mime_version : public t_header { public: string version; t_hdr_mime_version(); void set_version(const string &v); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_min_expires.cpp000066400000000000000000000020671277565361200212400ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_min_expires.h" #include "util.h" t_hdr_min_expires::t_hdr_min_expires() : t_header("Min-Expires") { time = 0; } void t_hdr_min_expires::set_time(unsigned long t) { populated = true; time = t; } string t_hdr_min_expires::encode_value(void) const { if (!populated) return ""; return ulong2str(time); } twinkle-1.10.1/src/parser/hdr_min_expires.h000066400000000000000000000020651277565361200207030ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Expires header #ifndef _HDR_MIN_EXPIRES_LENGTH #define _HDR_MIN_EXPIRES_LENGTH #include #include "header.h" using namespace std; class t_hdr_min_expires : public t_header { public: unsigned long time; // expiry time in seconds t_hdr_min_expires(); void set_time(unsigned long t); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_organization.cpp000066400000000000000000000020251277565361200214140ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_organization.h" t_hdr_organization::t_hdr_organization() : t_header("Organization") {}; void t_hdr_organization::set_name(const string &n) { populated = true; name = n; } string t_hdr_organization::encode_value(void) const { if (!populated) return ""; return name; } twinkle-1.10.1/src/parser/hdr_organization.h000066400000000000000000000020211277565361200210550ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Organization header #ifndef _H_HDR_ORGANIZATION #define _H_HDR_ORGANIZATION #include #include "header.h" using namespace std; class t_hdr_organization : public t_header { public: string name; t_hdr_organization(); void set_name(const string &n); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_p_asserted_identity.cpp000066400000000000000000000024111277565361200227510ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_p_asserted_identity.h" t_hdr_p_asserted_identity::t_hdr_p_asserted_identity() : t_header("P-Asserted-Identity") {} void t_hdr_p_asserted_identity::add_identity(const t_identity &identity) { populated = true; identity_list.push_back(identity); } string t_hdr_p_asserted_identity::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = identity_list.begin(); i != identity_list.end(); i++) { if (i != identity_list.begin()) s += ','; s += i->encode(); } return s; } twinkle-1.10.1/src/parser/hdr_p_asserted_identity.h000066400000000000000000000021771277565361200224270ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // RFC 3325 9.1 // P-Asserted-Identity header #ifndef _H_HDR_P_ASSERTED_IDENTITY #define _H_HDR_P_ASSERTED_IDENTITY #include #include "header.h" #include "identity.h" using namespace std; class t_hdr_p_asserted_identity : public t_header { public: list identity_list; t_hdr_p_asserted_identity(); void add_identity(const t_identity &identity); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_p_preferred_identity.cpp000066400000000000000000000024171277565361200231230ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_p_preferred_identity.h" t_hdr_p_preferred_identity::t_hdr_p_preferred_identity() : t_header("P-Preferred-Identity") {} void t_hdr_p_preferred_identity::add_identity(const t_identity &identity) { populated = true; identity_list.push_back(identity); } string t_hdr_p_preferred_identity::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = identity_list.begin(); i != identity_list.end(); i++) { if (i != identity_list.begin()) s += ','; s += i->encode(); } return s; } twinkle-1.10.1/src/parser/hdr_p_preferred_identity.h000066400000000000000000000022041277565361200225620ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // RFC 3325 9.2 // P-Preferred-Identity header #ifndef _H_HDR_P_PREFERRED_IDENTITY #define _H_HDR_P_PREFERRED_IDENTITY #include #include "header.h" #include "identity.h" using namespace std; class t_hdr_p_preferred_identity : public t_header { public: list identity_list; t_hdr_p_preferred_identity(); void add_identity(const t_identity &identity); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_priority.cpp000066400000000000000000000020111277565361200205640ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_priority.h" t_hdr_priority::t_hdr_priority() : t_header("Priority") {}; void t_hdr_priority::set_priority(const string &p) { populated = true; priority = p; } string t_hdr_priority::encode_value(void) const { if (!populated) return ""; return priority; } twinkle-1.10.1/src/parser/hdr_priority.h000066400000000000000000000020251277565361200202360ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Priority header #ifndef _H_HDR_PRIORITY_VERSION #define _H_HDR_PRIORITY_VERSION #include #include "header.h" using namespace std; class t_hdr_priority : public t_header { public: string priority; t_hdr_priority(); void set_priority(const string &p); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_privacy.cpp000066400000000000000000000026151277565361200203720ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include "definitions.h" #include "hdr_privacy.h" using namespace std; t_hdr_privacy::t_hdr_privacy() : t_header("Privacy") {}; void t_hdr_privacy::add_privacy(const string &privacy) { populated = true; privacy_list.push_back(privacy); } bool t_hdr_privacy::contains_privacy(const string &privacy) const { return (find(privacy_list.begin(), privacy_list.end(), privacy) != privacy_list.end()); } string t_hdr_privacy::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = privacy_list.begin(); i != privacy_list.end(); i++) { if (i != privacy_list.begin()) s += ";"; s += *i; } return s; } twinkle-1.10.1/src/parser/hdr_privacy.h000066400000000000000000000024541277565361200200400ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // RFC 3323 // Privacy header #ifndef _H_HDR_PRIVACY #define _H_HDR_PRIVACY #include #include #include "header.h" #define PRIVACY_HEADER "header" #define PRIVACY_SESSION "session" #define PRIVACY_USER "user" #define PRIVACY_NONE "none" #define PRIVACY_CRITICAL "critical" // RFC 3325 9.3 defines id privacy #define PRIVACY_ID "id" class t_hdr_privacy : public t_header { public: list privacy_list; t_hdr_privacy(); void add_privacy(const string &privacy); bool contains_privacy(const string &privacy) const; string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_proxy_authenticate.cpp000066400000000000000000000021251277565361200226300ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_proxy_authenticate.h" #include "definitions.h" t_hdr_proxy_authenticate::t_hdr_proxy_authenticate() : t_header("Proxy-Authenticate") {} void t_hdr_proxy_authenticate::set_challenge(const t_challenge &c) { populated = true; challenge = c; } string t_hdr_proxy_authenticate::encode_value(void) const { if (!populated) return ""; return challenge.encode(); } twinkle-1.10.1/src/parser/hdr_proxy_authenticate.h000066400000000000000000000021551277565361200223000ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Proxy-Authenticate header #ifndef _HDR_PROXY_AUTHENTICATE_H #define _HDR_PROXY_AUTHENTICATE_H #include #include #include "challenge.h" #include "header.h" using namespace std; class t_hdr_proxy_authenticate : public t_header { public: t_challenge challenge; t_hdr_proxy_authenticate(); void set_challenge(const t_challenge &c); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_proxy_authorization.cpp000066400000000000000000000045001277565361200230510ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_proxy_authorization.h" #include "definitions.h" t_hdr_proxy_authorization::t_hdr_proxy_authorization() : t_header("Proxy-Authorization") {} void t_hdr_proxy_authorization::add_credentials(const t_credentials &c) { populated = true; credentials_list.push_back(c); } string t_hdr_proxy_authorization::encode(void) const { string s; if (!populated) return s; // RFC 3261 20.28 // Each authorization should appear as a separate header for (list::const_iterator i = credentials_list.begin(); i != credentials_list.end(); i++) { s += header_name; s += ": "; s += i->encode(); s += CRLF; } return s; } string t_hdr_proxy_authorization::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = credentials_list.begin(); i != credentials_list.end(); i++) { if (i != credentials_list.begin()) s += ", "; s += i->encode(); } return s; } bool t_hdr_proxy_authorization::contains(const string &realm, const t_url &uri) const { for (list::const_iterator i = credentials_list.begin(); i != credentials_list.end(); i++) { if (i->digest_response.realm == realm && i->digest_response.digest_uri == uri) { return true; } } return false; } void t_hdr_proxy_authorization::remove_credentials(const string &realm, const t_url &uri) { for (list::iterator i = credentials_list.begin(); i != credentials_list.end(); i++) { if (i->digest_response.realm == realm && i->digest_response.digest_uri == uri) { credentials_list.erase(i); return; } } } twinkle-1.10.1/src/parser/hdr_proxy_authorization.h000066400000000000000000000026721277565361200225260ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Proxy-Authorization header #ifndef _HDR_PROXY_AUTHORIZATION #define _HDR_PROXY_AUTHORIZATION #include #include #include "credentials.h" #include "header.h" #include "parameter.h" #include "sockets/url.h" using namespace std; class t_hdr_proxy_authorization : public t_header { public: list credentials_list; t_hdr_proxy_authorization(); void add_credentials(const t_credentials &c); string encode(void) const; string encode_value(void) const; // Return true if the header contains credentials for a realm/dest bool contains(const string &realm, const t_url &uri) const; // Remove credentials for a realm/dest void remove_credentials(const string &realm, const t_url &uri); }; #endif twinkle-1.10.1/src/parser/hdr_proxy_require.cpp000066400000000000000000000023061277565361200216270ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_proxy_require.h" t_hdr_proxy_require::t_hdr_proxy_require() : t_header("Proxy-Require") {}; void t_hdr_proxy_require::add_feature(const string &f) { populated = true; features.push_back(f); } string t_hdr_proxy_require::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = features.begin(); i != features.end(); i++) { if (i != features.begin()) s += ", "; s += *i; } return s; } twinkle-1.10.1/src/parser/hdr_proxy_require.h000066400000000000000000000020361277565361200212740ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Proxy-Require header #ifndef _H_HDR_PROXY_REQUIRE #define _H_HDR_PROXY_REQUIRE #include #include #include "header.h" class t_hdr_proxy_require : public t_header { public: list features; t_hdr_proxy_require(); void add_feature(const string &f); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_rack.cpp000066400000000000000000000030151277565361200176300ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_rack.h" #include "util.h" t_hdr_rack::t_hdr_rack() : t_header("RAck") { cseq_nr = 0; resp_nr = 0; method = INVITE; } void t_hdr_rack::set_cseq_nr(unsigned long l) { populated = true; cseq_nr = l; } void t_hdr_rack::set_resp_nr(unsigned long l) { populated = true; resp_nr = l; } void t_hdr_rack::set_method(t_method m, const string &unknown) { populated = true; method = m; unknown_method = unknown; } void t_hdr_rack::set_method(const string &s) { populated = true; method = str2method(s); if (method == METHOD_UNKNOWN) { unknown_method = s; } } string t_hdr_rack::encode_value(void) const { string s; if (!populated) return s; s = ulong2str(resp_nr) + ' '; s += ulong2str(cseq_nr); s += ' '; s += method2str(method, unknown_method); return s; } twinkle-1.10.1/src/parser/hdr_rack.h000066400000000000000000000023711277565361200173010ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // RAck header // RFC 3262 #ifndef _HDR_RACK #define _HDR_RACK #include #include "header.h" #include "definitions.h" using namespace std; class t_hdr_rack : public t_header { public: unsigned long cseq_nr; unsigned long resp_nr; t_method method; string unknown_method; // set if method is UNKNOWN t_hdr_rack(); void set_cseq_nr(unsigned long l); void set_resp_nr(unsigned long l); void set_method(t_method m, const string &unknown = ""); void set_method(const string &s); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_record_route.cpp000066400000000000000000000032411277565361200214050ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_record_route.h" #include "parse_ctrl.h" #include "util.h" t_hdr_record_route::t_hdr_record_route() : t_header("Record-Route") {} void t_hdr_record_route::add_route(const t_route &r) { populated = true; route_list.push_back(r); } string t_hdr_record_route::encode(void) const { return (t_parser::multi_values_as_list ? t_header::encode() : encode_multi_header()); } string t_hdr_record_route::encode_multi_header(void) const { string s; if (!populated) return s; for (list::const_iterator i = route_list.begin(); i != route_list.end(); i++) { s += header_name; s += ": "; s += i->encode(); s += CRLF; } return s; } string t_hdr_record_route::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = route_list.begin(); i != route_list.end(); i++) { if (i != route_list.begin()) s += ","; s += i->encode(); } return s; } twinkle-1.10.1/src/parser/hdr_record_route.h000066400000000000000000000022711277565361200210540ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Record-Route header #ifndef _H_HDR_RECORD_ROUTE #define _H_HDR_RECORD_ROUTE #include #include #include "route.h" #include "header.h" #include "parameter.h" #include "sockets/url.h" using namespace std; class t_hdr_record_route : public t_header { public: list route_list; t_hdr_record_route(); void add_route(const t_route &r); string encode(void) const; string encode_multi_header(void) const; string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_refer_sub.cpp000066400000000000000000000025021277565361200206640ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_refer_sub.h" t_hdr_refer_sub::t_hdr_refer_sub() : t_header("Refer-Sub"), create_refer_sub(true) {} void t_hdr_refer_sub::set_create_refer_sub(bool on) { populated = true; create_refer_sub = on; } void t_hdr_refer_sub::add_extension(const t_parameter &p) { populated = true; extensions.push_back(p); } void t_hdr_refer_sub::set_extensions(const list &l) { populated = true; extensions = l; } string t_hdr_refer_sub::encode_value(void) const { string s; if (!populated) return s; s = (create_refer_sub ? "true" : "false"); s += param_list2str(extensions); return s; } twinkle-1.10.1/src/parser/hdr_refer_sub.h000066400000000000000000000023051277565361200203320ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Refer-Sub header // RFC 4488 #ifndef _H_HDR_REFER_SUB #define _H_HDR_REFER_SUB #include #include #include "header.h" #include "parameter.h" using namespace std; class t_hdr_refer_sub : public t_header { public: bool create_refer_sub; list extensions; t_hdr_refer_sub(); void set_create_refer_sub(bool on); void add_extension(const t_parameter &p); void set_extensions(const list &l); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_refer_to.cpp000066400000000000000000000031151277565361200205160ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_refer_to.h" #include "definitions.h" #include "parse_ctrl.h" #include "util.h" t_hdr_refer_to::t_hdr_refer_to() : t_header("Refer-To", "r") {} void t_hdr_refer_to::set_display(const string &d) { populated = true; display = d; } void t_hdr_refer_to::set_uri(const string &u) { populated = true; uri.set_url(u); } void t_hdr_refer_to::set_uri(const t_url &u) { populated = true; uri = u; } void t_hdr_refer_to::set_params(const list &l) { populated = true; params = l; } void t_hdr_refer_to::add_param(const t_parameter &p) { populated = true; params.push_back(p); } string t_hdr_refer_to::encode_value(void) const { string s; if (!populated) return s; if (display.size() > 0) { s += '"'; s += escape(display, '"'); s += '"'; s += ' '; } s += '<'; s += uri.encode(); s += '>'; s += param_list2str(params); return s; } twinkle-1.10.1/src/parser/hdr_refer_to.h000066400000000000000000000024171277565361200201670ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // RFC 3515 // Refer-To header #ifndef _H_HDR_REFER_TO #define _H_HDR_REFER_TO #include #include "header.h" #include "parameter.h" #include "sockets/url.h" using namespace std; class t_hdr_refer_to : public t_header { public: string display; // display name t_url uri; list params; t_hdr_refer_to(); void set_display(const string &d); void set_uri(const string &u); void set_uri(const t_url &u); void set_params(const list &l); void add_param(const t_parameter &p); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_referred_by.cpp000066400000000000000000000033651277565361200212100ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_referred_by.h" #include "definitions.h" #include "parse_ctrl.h" #include "util.h" t_hdr_referred_by::t_hdr_referred_by() : t_header("Referred-By", "b") {} void t_hdr_referred_by::set_display(const string &d) { populated = true; display = d; } void t_hdr_referred_by::set_uri(const string &u) { populated = true; uri.set_url(u); } void t_hdr_referred_by::set_uri(const t_url &u) { populated = true; uri = u; } void t_hdr_referred_by::set_cid(const string &c) { populated = true; cid = c; } void t_hdr_referred_by::set_params(const list &l) { populated = true; params = l; } void t_hdr_referred_by::add_param(const t_parameter &p) { populated = true; params.push_back(p); } string t_hdr_referred_by::encode_value(void) const { string s; if (!populated) return s; if (display.size() > 0) { s += '"'; s += escape(display, '"'); s += '"'; s += ' '; } s += '<'; s += uri.encode(); s += '>'; if (cid.size() > 0) { s += ";cid="; s += cid; } s += param_list2str(params); return s; } twinkle-1.10.1/src/parser/hdr_referred_by.h000066400000000000000000000025151277565361200206510ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // RFC 3892 // Referred-By header #ifndef _H_HDR_REFERRED_BY #define _H_HDR_REFERRED_BY #include #include "header.h" #include "parameter.h" #include "sockets/url.h" using namespace std; class t_hdr_referred_by : public t_header { public: string display; // display name t_url uri; string cid; list params; t_hdr_referred_by(); void set_display(const string &d); void set_uri(const string &u); void set_uri(const t_url &u); void set_cid(const string &c); void set_params(const list &l); void add_param(const t_parameter &p); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_replaces.cpp000066400000000000000000000033651277565361200205160ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_replaces.h" t_hdr_replaces::t_hdr_replaces() : t_header("Replaces"), early_only(false) {} void t_hdr_replaces::set_call_id(const string &id) { populated = true; call_id = id; } void t_hdr_replaces::set_to_tag(const string &tag) { populated = true; to_tag = tag; } void t_hdr_replaces::set_from_tag(const string &tag) { populated = true; from_tag = tag; } void t_hdr_replaces::set_early_only(const bool on) { populated = true; early_only = on; } void t_hdr_replaces::set_params(const list &l) { populated = true; params = l; } void t_hdr_replaces::add_param(const t_parameter &p) { populated = true; params.push_back(p); } string t_hdr_replaces::encode_value(void) const { string s; if (!populated) return s; s += call_id; s += ";to-tag="; s += to_tag; s += ";from-tag="; s += from_tag; if (early_only) { s += ";early-only"; } s += param_list2str(params); return s; } bool t_hdr_replaces::is_valid(void) const { return !(call_id.empty() || to_tag.empty() || from_tag.empty()); } twinkle-1.10.1/src/parser/hdr_replaces.h000066400000000000000000000025621277565361200201610ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Replaces header // RFC 3891 #ifndef _H_HDR_REPLACES #define _H_HDR_REPLACES #include #include #include "header.h" #include "parameter.h" using namespace std; class t_hdr_replaces : public t_header { public: string call_id; string to_tag; string from_tag; bool early_only; list params; t_hdr_replaces(); void set_call_id(const string &id); void set_to_tag(const string &tag); void set_from_tag(const string &tag); void set_early_only(const bool on); void set_params(const list &l); void add_param(const t_parameter &p); string encode_value(void) const; bool is_valid(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_reply_to.cpp000066400000000000000000000030211277565361200205420ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_reply_to.h" #include "definitions.h" t_hdr_reply_to::t_hdr_reply_to() : t_header("Reply-To") {} void t_hdr_reply_to::set_display(const string &d) { populated = true; display = d; } void t_hdr_reply_to::set_uri(const string &u) { populated = true; uri.set_url(u); } void t_hdr_reply_to::set_uri(const t_url &u) { populated = true; uri = u; } void t_hdr_reply_to::set_params(const list &l) { populated = true; params = l; } void t_hdr_reply_to::add_param(const t_parameter &p) { populated = true; params.push_back(p); } string t_hdr_reply_to::encode_value(void) const { string s; if (!populated) return s; if (display.size() > 0) { s += '"'; s += display; s += '"'; s += ' '; } s += '<'; s += uri.encode(); s += '>'; s += param_list2str(params); return s; } twinkle-1.10.1/src/parser/hdr_reply_to.h000066400000000000000000000024031277565361200202120ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Reply-To header #ifndef _H_HDR_REPLY_TO #define _H_HDR_REPLY_TO #include #include "header.h" #include "parameter.h" #include "sockets/url.h" using namespace std; class t_hdr_reply_to : public t_header { public: string display; // display name t_url uri; list params; t_hdr_reply_to(); void set_display(const string &d); void set_uri(const string &u); void set_uri(const t_url &u); void set_params(const list &l); void add_param(const t_parameter &p); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_request_disposition.cpp000066400000000000000000000111471277565361200230310ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_request_disposition.h" #include #include "util.h" t_hdr_request_disposition::t_hdr_request_disposition() : t_header("Request-Disposition", "d"), proxy_directive(PROXY_NULL), cancel_directive(CANCEL_NULL), fork_directive(FORK_NULL), recurse_directive(RECURSE_NULL), parallel_directive(PARALLEL_NULL), queue_directive(QUEUE_NULL) {} void t_hdr_request_disposition::set_proxy_directive(t_proxy_directive directive) { populated = true; proxy_directive = directive; } void t_hdr_request_disposition::set_cancel_directive(t_cancel_directive directive) { populated = true; cancel_directive = directive; } void t_hdr_request_disposition::set_fork_directive(t_fork_directive directive) { populated = true; fork_directive = directive; } void t_hdr_request_disposition::set_recurse_directive(t_recurse_directive directive) { populated = true; recurse_directive = directive; } void t_hdr_request_disposition::set_parallel_directive(t_parallel_directive directive) { populated = true; parallel_directive = directive; } void t_hdr_request_disposition::set_queue_directive(t_queue_directive directive) { populated = true; queue_directive = directive; } bool t_hdr_request_disposition::set_directive(const string &s) { if (s == REQDIS_PROXY) { if (proxy_directive == REDIRECT) return false; set_proxy_directive(PROXY); return true; } if (s == REQDIS_REDIRECT) { if (proxy_directive == PROXY) return false; set_proxy_directive(REDIRECT); return true; } if (s == REQDIS_CANCEL) { if (cancel_directive == NO_CANCEL) return false; set_cancel_directive(CANCEL); return true; } if (s == REQDIS_NO_CANCEL) { if (cancel_directive == CANCEL) return false; set_cancel_directive(NO_CANCEL); return true; } if (s == REQDIS_FORK) { if (fork_directive == NO_FORK) return false; set_fork_directive(FORK); return true; } if (s == REQDIS_NO_FORK) { if (fork_directive == FORK) return false; set_fork_directive(NO_FORK); return true; } if (s == REQDIS_RECURSE) { if (recurse_directive == NO_RECURSE) return false; set_recurse_directive(RECURSE); return true; } if (s == REQDIS_NO_RECURSE) { if (recurse_directive == RECURSE) return false; set_recurse_directive(NO_RECURSE); return true; } if (s == REQDIS_PARALLEL) { if (parallel_directive == SEQUENTIAL) return false; set_parallel_directive(PARALLEL); return true; } if (s == REQDIS_SEQUENTIAL) { if (parallel_directive == PARALLEL) return false; set_parallel_directive(SEQUENTIAL); return true; } if (s == REQDIS_QUEUE) { if (queue_directive == NO_QUEUE) return false; set_queue_directive(QUEUE); return true; } if (s == REQDIS_NO_QUEUE) { if (queue_directive == QUEUE) return false; set_queue_directive(NO_QUEUE); return true; } return false; } string t_hdr_request_disposition::encode_value(void) const { if (!populated) return ""; vector v; switch (proxy_directive) { case PROXY: v.push_back(REQDIS_PROXY); break; case REDIRECT: v.push_back(REQDIS_REDIRECT); break; default: break; } switch (cancel_directive) { case CANCEL: v.push_back(REQDIS_CANCEL); break; case NO_CANCEL: v.push_back(REQDIS_NO_CANCEL); break; default: break; } switch (fork_directive) { case FORK: v.push_back(REQDIS_FORK); break; case NO_FORK: v.push_back(REQDIS_NO_FORK); break; default: break; } switch (recurse_directive) { case RECURSE: v.push_back(REQDIS_RECURSE); break; case NO_RECURSE: v.push_back(REQDIS_NO_RECURSE); break; default: break; } switch (parallel_directive) { case PARALLEL: v.push_back(REQDIS_PARALLEL); break; case SEQUENTIAL: v.push_back(REQDIS_SEQUENTIAL); break; default: break; } switch (queue_directive) { case QUEUE: v.push_back(REQDIS_QUEUE); break; case NO_QUEUE: v.push_back(REQDIS_NO_QUEUE); break; default: break; } string s = join_strings(v, ","); return s; } twinkle-1.10.1/src/parser/hdr_request_disposition.h000066400000000000000000000053251277565361200224770ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Request-Disposition header (RFC 3841) */ #ifndef _H_HDR_REQUEST_DISPOSITION #define _H_HDR_REQUEST_DISPOSITION #include #include "header.h" using namespace std; #define REQDIS_PROXY "proxy" #define REQDIS_REDIRECT "redirect" #define REQDIS_CANCEL "cancel" #define REQDIS_NO_CANCEL "no-cancel" #define REQDIS_FORK "fork" #define REQDIS_NO_FORK "no-fork" #define REQDIS_RECURSE "recurse" #define REQDIS_NO_RECURSE "no-recurse" #define REQDIS_PARALLEL "parallel" #define REQDIS_SEQUENTIAL "sequential" #define REQDIS_QUEUE "queue" #define REQDIS_NO_QUEUE "no-queue" /** Request-Disposition header (RFC 3841) */ class t_hdr_request_disposition : public t_header { public: enum t_proxy_directive { PROXY_NULL, PROXY, REDIRECT }; enum t_cancel_directive { CANCEL_NULL, CANCEL, NO_CANCEL }; enum t_fork_directive { FORK_NULL, FORK, NO_FORK }; enum t_recurse_directive { RECURSE_NULL, RECURSE, NO_RECURSE }; enum t_parallel_directive { PARALLEL_NULL, PARALLEL, SEQUENTIAL }; enum t_queue_directive { QUEUE_NULL, QUEUE, NO_QUEUE }; t_proxy_directive proxy_directive; t_cancel_directive cancel_directive; t_fork_directive fork_directive; t_recurse_directive recurse_directive; t_parallel_directive parallel_directive; t_queue_directive queue_directive; t_hdr_request_disposition(); void set_proxy_directive(t_proxy_directive directive); void set_cancel_directive(t_cancel_directive directive); void set_fork_directive(t_fork_directive directive); void set_recurse_directive(t_recurse_directive directive); void set_parallel_directive(t_parallel_directive directive); void set_queue_directive(t_queue_directive directive); /** * Set a directive using one of the tokens define in RFC 3841 * @param s [in] Directive token. * @return True if directive set. False if directive is invalid or * conflicts with exisiting directives. */ bool set_directive(const string &s); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_require.cpp000066400000000000000000000034031277565361200203650ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_require.h" t_hdr_require::t_hdr_require() : t_header("Require") {}; void t_hdr_require::add_feature(const string &f) { populated = true; if (!contains(f)) { features.push_back(f); } } void t_hdr_require::add_features(const list &l) { if (l.empty()) return; for (list::const_iterator i = l.begin(); i != l.end(); i++) { add_feature(*i); } populated = true; } void t_hdr_require::del_feature(const string &f) { features.remove(f); } bool t_hdr_require::contains(const string &f) const { if (!populated) return false; for (list::const_iterator i = features.begin(); i != features.end(); i++) { if (*i == f) return true; } return false; } string t_hdr_require::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = features.begin(); i != features.end(); i++) { if (i != features.begin()) s += ", "; s += *i; } return s; } void t_hdr_require::unpopulate(void) { populated = false; features.clear(); } twinkle-1.10.1/src/parser/hdr_require.h000066400000000000000000000022161277565361200200330ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Require header #ifndef _H_HDR_REQUIRE #define _H_HDR_REQUIRE #include #include #include "header.h" class t_hdr_require : public t_header { public: list features; t_hdr_require(); void add_feature(const string &f); void add_features(const list &l); void del_feature(const string &f); bool contains(const string &f) const; string encode_value(void) const; void unpopulate(void); }; #endif twinkle-1.10.1/src/parser/hdr_retry_after.cpp000066400000000000000000000030511277565361200212360ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_retry_after.h" #include "util.h" t_hdr_retry_after::t_hdr_retry_after() : t_header("Retry-After") { time = 0; duration = 0; } void t_hdr_retry_after::set_time(unsigned long t) { populated = true; time = t; } void t_hdr_retry_after::set_comment(const string &c) { populated = true; comment = c; } void t_hdr_retry_after::set_duration(unsigned long d) { populated = true; duration = d; } void t_hdr_retry_after::add_param(const t_parameter &p) { populated = true; params.push_back(p); } string t_hdr_retry_after::encode_value(void) const { string s; if (!populated) return s; s = ulong2str(time); if (comment.size() > 0) { s += " ("; s += comment; s += ')'; } if (duration > 0) { s += ";duration="; s += ulong2str(duration); } s += param_list2str(params); return s; } twinkle-1.10.1/src/parser/hdr_retry_after.h000066400000000000000000000024201277565361200207020ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Retry-After header #ifndef _H_HDR_RETRY_AFTER #define _H_HDR_RETRY_AFTER #include #include #include "header.h" #include "parameter.h" using namespace std; class t_hdr_retry_after : public t_header { public: unsigned long time; // in seconds string comment; unsigned long duration; // in seconds list params; t_hdr_retry_after(); void set_time(unsigned long t); void set_comment(const string &c); void set_duration(unsigned long d); void add_param(const t_parameter &p); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_route.cpp000066400000000000000000000031671277565361200200560ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_route.h" #include "parse_ctrl.h" t_hdr_route::t_hdr_route() : t_header("Route") { route_to_first_route = false; } void t_hdr_route::add_route(const t_route &r) { populated = true; route_list.push_back(r); } string t_hdr_route::encode(void) const { return (t_parser::multi_values_as_list ? t_header::encode() : encode_multi_header()); } string t_hdr_route::encode_multi_header(void) const { string s; if (!populated) return s; for (list::const_iterator i = route_list.begin(); i != route_list.end(); i++) { s += header_name; s += ": "; s += i->encode(); s += CRLF; } return s; } string t_hdr_route::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = route_list.begin(); i != route_list.end(); i++) { if (i != route_list.begin()) s += ","; s += i->encode(); } return s; } twinkle-1.10.1/src/parser/hdr_route.h000066400000000000000000000024421277565361200175160ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Route header #ifndef _H_HDR_ROUTE #define _H_HDR_ROUTE #include #include #include "route.h" #include "header.h" #include "parameter.h" using namespace std; class t_hdr_route : public t_header { public: list route_list; // If route_to_first_route == true, then the request must be routed // to the first route in the list. Otherwise to the request URI. bool route_to_first_route; t_hdr_route(); void add_route(const t_route &r); string encode(void) const; string encode_multi_header(void) const; string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_rseq.cpp000066400000000000000000000021651277565361200176670ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_rseq.h" #include "util.h" t_hdr_rseq::t_hdr_rseq() : t_header("RSeq") { resp_nr = 0; } void t_hdr_rseq::set_resp_nr(unsigned long l) { populated = true; resp_nr = l; } string t_hdr_rseq::encode_value(void) const { if (!populated) return ""; return ulong2str(resp_nr); } bool t_hdr_rseq::operator==(const t_hdr_rseq &h) const { return (resp_nr == h.resp_nr); } twinkle-1.10.1/src/parser/hdr_rseq.h000066400000000000000000000021111277565361200173230ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // RSeq header // RFC 3262 #ifndef _HDR_RSEQ #define _HDR_RSEQ #include #include "header.h" #include "definitions.h" using namespace std; class t_hdr_rseq : public t_header { public: unsigned long resp_nr; t_hdr_rseq(); void set_resp_nr(unsigned long l); string encode_value(void) const; bool operator==(const t_hdr_rseq &h) const; }; #endif twinkle-1.10.1/src/parser/hdr_server.cpp000066400000000000000000000033041277565361200202170ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_server.h" #include "util.h" t_server::t_server() {} t_server::t_server(const string &_product, const string &_version, const string &_comment) { product = _product; version = _version; comment = _comment; } string t_server::encode(void) const { string s; s = product; if (version.size() > 0) { s += '/'; s += version; } if (comment.size() > 0) { if (s.size() > 0) s += ' '; s += "("; s += comment; s += ')'; } return s; } t_hdr_server::t_hdr_server() : t_header("Server") {}; void t_hdr_server::add_server(const t_server &s) { populated = true; server_info.push_back(s); } string t_hdr_server::get_server_info(void) const { string s; for (list::const_iterator i = server_info.begin(); i != server_info.end(); i++ ) { if (i != server_info.begin()) s += ' '; s += i->encode(); } return s; } string t_hdr_server::encode_value(void) const { if (!populated) return ""; return get_server_info(); } twinkle-1.10.1/src/parser/hdr_server.h000066400000000000000000000025041277565361200176650ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Server header #ifndef _H_HDR_SERVER #define _H_HDR_SERVER #include #include #include "header.h" using namespace std; class t_server { public: string product; string version; string comment; t_server(); t_server(const string &_product, const string &_version, const string &_comment = ""); string encode(void) const; }; class t_hdr_server : public t_header { public: list server_info; t_hdr_server(); void add_server(const t_server &s); // Get a string representation of server_info string get_server_info(void) const; string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_service_route.cpp000066400000000000000000000032511277565361200215700ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_service_route.h" #include "parse_ctrl.h" #include "util.h" t_hdr_service_route::t_hdr_service_route() : t_header("Service-Route") {} void t_hdr_service_route::add_route(const t_route &r) { populated = true; route_list.push_back(r); } string t_hdr_service_route::encode(void) const { return (t_parser::multi_values_as_list ? t_header::encode() : encode_multi_header()); } string t_hdr_service_route::encode_multi_header(void) const { string s; if (!populated) return s; for (list::const_iterator i = route_list.begin(); i != route_list.end(); i++) { s += header_name; s += ": "; s += i->encode(); s += CRLF; } return s; } string t_hdr_service_route::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = route_list.begin(); i != route_list.end(); i++) { if (i != route_list.begin()) s += ","; s += i->encode(); } return s; } twinkle-1.10.1/src/parser/hdr_service_route.h000066400000000000000000000022761277565361200212430ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Service-Route header #ifndef _H_HDR_SERVICE_ROUTE #define _H_HDR_SERVICE_ROUTE #include #include #include "route.h" #include "header.h" #include "parameter.h" #include "sockets/url.h" using namespace std; class t_hdr_service_route : public t_header { public: list route_list; t_hdr_service_route(); void add_route(const t_route &r); string encode(void) const; string encode_multi_header(void) const; string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_sip_etag.cpp000066400000000000000000000017531277565361200205120ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_sip_etag.h" t_hdr_sip_etag::t_hdr_sip_etag() : t_header("SIP-ETag") {}; void t_hdr_sip_etag::set_etag(const string &_etag) { populated = true; etag = _etag; } string t_hdr_sip_etag::encode_value(void) const { if (!populated) return ""; return etag; } twinkle-1.10.1/src/parser/hdr_sip_etag.h000066400000000000000000000022661277565361200201570ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * SIP-ETag header (RFC 3903) */ #ifndef _HDR_SIP_ETAG_H #define _HDR_SIP_ETAG_H #include #include "header.h" using namespace std; /** SIP-ETag header (RFC 3903) */ class t_hdr_sip_etag : public t_header { public: string etag; /**< Entity tag. */ /** Constructor. */ t_hdr_sip_etag(); /** * Set entity tag. * @param _etag [in] Entity tag to set. */ void set_etag(const string &_etag); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_sip_if_match.cpp000066400000000000000000000021201277565361200213310ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_sip_if_match.h" t_hdr_sip_if_match::t_hdr_sip_if_match() : t_header("SIP-If-Match") {}; void t_hdr_sip_if_match::set_etag(const string &_etag) { populated = true; etag = _etag; } string t_hdr_sip_if_match::encode_value(void) const { if (!populated) return ""; return etag; } void t_hdr_sip_if_match::clear(void) { etag.clear(); populated = false; } twinkle-1.10.1/src/parser/hdr_sip_if_match.h000066400000000000000000000023751277565361200210120ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * SIP-If-Match header (RFC 3903) */ #ifndef _HDR_SIP_IF_MATCH_H #define _HDR_SIP_IF_MATCH_H #include #include "header.h" using namespace std; /** SIP-If-Match header (RFC 3903) */ class t_hdr_sip_if_match : public t_header { public: string etag; /**< Entity tag. */ /** Constructor. */ t_hdr_sip_if_match(); /** * Set entity tag. * @param _etag [in] Entity tag to set. */ void set_etag(const string &_etag); /** Clear the header. */ void clear(void); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_subject.cpp000066400000000000000000000020351277565361200203500ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_subject.h" #include "parse_ctrl.h" t_hdr_subject::t_hdr_subject() : t_header("Subject", "s") {}; void t_hdr_subject::set_subject(const string &s) { populated = true; subject = s; } string t_hdr_subject::encode_value(void) const { if (!populated) return ""; return subject; } twinkle-1.10.1/src/parser/hdr_subject.h000066400000000000000000000017761277565361200200300ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Subject header #ifndef _H_HDR_SUBJECT #define _H_HDR_SUBJECT #include #include "header.h" using namespace std; class t_hdr_subject : public t_header { public: string subject; t_hdr_subject(); void set_subject(const string &s); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_subscription_state.cpp000066400000000000000000000034511277565361200226400ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_subscription_state.h" #include "util.h" t_hdr_subscription_state::t_hdr_subscription_state() : t_header("Subscription-State") { expires = 0; retry_after = 0; } void t_hdr_subscription_state::set_substate(const string &s) { populated = true; substate = s; } void t_hdr_subscription_state::set_reason(const string &s) { populated = true; reason = s; } void t_hdr_subscription_state::set_expires(unsigned long e) { populated = true; expires = e; } void t_hdr_subscription_state::set_retry_after(unsigned long r) { populated = true; retry_after = r; } void t_hdr_subscription_state::add_extension(const t_parameter &p) { populated = true; extensions.push_back(p); } string t_hdr_subscription_state::encode_value(void) const { string s; if (!populated) return s; s = substate; if (reason.size() > 0) { s += ";reason="; s += reason; } if (expires > 0) { s += ";expires="; s += ulong2str(expires); } if (retry_after > 0) { s += ";retry-after="; s += ulong2str(retry_after); } s += param_list2str(extensions); return s; } twinkle-1.10.1/src/parser/hdr_subscription_state.h000066400000000000000000000033551277565361200223100ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Subscription-State header // RFC 3265 #ifndef _HDR_SUBSCRIPTION_STATE #define _HDR_SUBSCRIPTION_STATE #include #include #include "header.h" #include "parameter.h" // Subscription states #define SUBSTATE_ACTIVE "active" #define SUBSTATE_PENDING "pending" #define SUBSTATE_TERMINATED "terminated" // Event reasons #define EV_REASON_DEACTIVATED "deactivated" #define EV_REASON_PROBATION "probation" #define EV_REASON_REJECTED "rejected" #define EV_REASON_TIMEOUT "timeout" #define EV_REASON_GIVEUP "giveup" #define EV_REASON_NORESOURCE "noresource" using namespace std; class t_hdr_subscription_state : public t_header { public: string substate; string reason; unsigned long expires; unsigned long retry_after; list extensions; t_hdr_subscription_state(); void set_substate(const string &s); void set_reason(const string &s); void set_expires(unsigned long e); void set_retry_after(unsigned long r); void add_extension(const t_parameter &p); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_supported.cpp000066400000000000000000000033441277565361200207420ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_supported.h" #include "parse_ctrl.h" t_hdr_supported::t_hdr_supported() : t_header("Supported", "k") {}; void t_hdr_supported::add_feature(const string &f) { populated = true; if (!contains(f)) { features.push_back(f); } } void t_hdr_supported::add_features(const list &l) { if (l.empty()) return; for (list::const_iterator i = l.begin(); i != l.end(); i++) { add_feature(*i); } populated = true; } void t_hdr_supported::set_empty(void) { populated = true; features.clear(); } bool t_hdr_supported::contains(const string &f) const { if (!populated) return false; for (list::const_iterator i = features.begin(); i != features.end(); i++) { if (*i == f) return true; } return false; } string t_hdr_supported::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = features.begin(); i != features.end(); i++) { if (i != features.begin()) s += ","; s += *i; } return s; } twinkle-1.10.1/src/parser/hdr_supported.h000066400000000000000000000025511277565361200204060ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Supported header #ifndef _H_HDR_SUPPORTED #define _H_HDR_SUPPORTED #include #include #include "header.h" #define EXT_100REL "100rel" // RFC 3262 #define EXT_REPLACES "replaces" // RFC 3891 #define EXT_NOREFERSUB "norefersub" // RFC 4488 class t_hdr_supported : public t_header { public: list features; t_hdr_supported(); void add_feature(const string &f); void add_features(const list &l); // Clear the list of features, but make the header 'populated'. // An empty header will be in the message. void set_empty(void); bool contains(const string &f) const; string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_timestamp.cpp000066400000000000000000000023471277565361200207220ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_timestamp.h" #include "util.h" t_hdr_timestamp::t_hdr_timestamp() : t_header("Timestamp") { timestamp = 0; delay = 0; } void t_hdr_timestamp::set_timestamp(float t) { populated = true; timestamp = t; } void t_hdr_timestamp::set_delay(float d) { populated = true; delay = d; } string t_hdr_timestamp::encode_value(void) const { string s; if (!populated) return s; s += float2str(timestamp, 3); if (delay != 0) { s += " "; s += float2str(delay, 3); } return s; } twinkle-1.10.1/src/parser/hdr_timestamp.h000066400000000000000000000020541277565361200203620ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Timestamp header #ifndef _H_HDR_TIMESTAMP #define _H_HDR_TIMESTAMP #include #include "header.h" using namespace std; class t_hdr_timestamp : public t_header { public: float timestamp; float delay; t_hdr_timestamp(); void set_timestamp(float t); void set_delay(float d); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_to.cpp000066400000000000000000000032161277565361200173350ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_to.h" #include "definitions.h" #include "parse_ctrl.h" #include "util.h" t_hdr_to::t_hdr_to() : t_header("To", "t") {} void t_hdr_to::set_display(const string &d) { populated = true; display = d; } void t_hdr_to::set_uri(const string &u) { populated = true; uri.set_url(u); } void t_hdr_to::set_uri(const t_url &u) { populated = true; uri = u; } void t_hdr_to::set_tag(const string &t) { populated = true; tag = t; } void t_hdr_to::set_params(const list &l) { populated = true; params = l; } void t_hdr_to::add_param(const t_parameter &p) { populated = true; params.push_back(p); } string t_hdr_to::encode_value(void) const { string s; if (!populated) return s; if (display.size() > 0) { s += '"'; s += escape(display, '"'); s += '"'; s += ' '; } s += '<'; s += uri.encode(); s += '>'; if (tag != "") { s += ";tag="; s += tag; } s += param_list2str(params); return s; } twinkle-1.10.1/src/parser/hdr_to.h000066400000000000000000000024241277565361200170020ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // To header #ifndef _H_HDR_TO #define _H_HDR_TO #include #include "header.h" #include "parameter.h" #include "sockets/url.h" using namespace std; class t_hdr_to : public t_header { public: string display; // display name t_url uri; string tag; list params; t_hdr_to(); void set_display(const string &d); void set_uri(const string &u); void set_uri(const t_url &u); void set_tag(const string &t); void set_params(const list &l); void add_param(const t_parameter &p); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_unsupported.cpp000066400000000000000000000030241277565361200213000ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_unsupported.h" t_hdr_unsupported::t_hdr_unsupported() : t_header("Unsupported") {}; void t_hdr_unsupported::add_feature(const string &f) { populated = true; features.push_back(f); } void t_hdr_unsupported::set_features(const list &_features) { populated = true; features = _features; } bool t_hdr_unsupported::contains(const string &f) const { if (!populated) return false; for (list::const_iterator i = features.begin(); i != features.end(); i++) { if (*i == f) return true; } return false; } string t_hdr_unsupported::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = features.begin(); i != features.end(); i++) { if (i != features.begin()) s += ","; s += *i; } return s; } twinkle-1.10.1/src/parser/hdr_unsupported.h000066400000000000000000000021561277565361200207520ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Unsupported header #ifndef _H_HDR_UNSUPPORTED #define _H_HDR_UNSUPPORTED #include #include #include "header.h" class t_hdr_unsupported : public t_header { public: list features; t_hdr_unsupported(); void add_feature(const string &f); void set_features(const list &_features); bool contains(const string &f) const; string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_user_agent.cpp000066400000000000000000000024371277565361200210530ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_user_agent.h" #include "util.h" t_hdr_user_agent::t_hdr_user_agent() : t_header("User-Agent") {}; void t_hdr_user_agent::add_server(const t_server &s) { populated = true; ua_info.push_back(s); } string t_hdr_user_agent::get_ua_info(void) const { string s; for (list::const_iterator i = ua_info.begin(); i != ua_info.end(); i++ ) { if (i != ua_info.begin()) s += ' '; s += i->encode(); } return s; } string t_hdr_user_agent::encode_value(void) const { if (!populated) return ""; return get_ua_info(); } twinkle-1.10.1/src/parser/hdr_user_agent.h000066400000000000000000000022161277565361200205130ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // User-Agent header #ifndef _H_HDR_USER_AGENT #define _H_HDR_USER_AGENT #include #include #include "header.h" #include "hdr_server.h" using namespace std; class t_hdr_user_agent : public t_header { public: list ua_info; t_hdr_user_agent(); void add_server(const t_server &s); // Get string representation of ua_info; string get_ua_info(void) const; string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_via.cpp000066400000000000000000000113611277565361200174720ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include "definitions.h" #include "hdr_via.h" #include "util.h" #include "parse_ctrl.h" #include "protocol.h" #include "sockets/url.h" t_via::t_via() { port = 0; ttl = 0; rport_present = false; rport = 0; } t_via::t_via(const string &_host, const int _port, bool add_rport) { protocol_name = "SIP"; protocol_version = SIP_VERSION; transport = "UDP"; host = _host; branch = RFC3261_COOKIE + random_token(8); if (_port != get_default_port("sip")) { port = _port; } else { port = 0; } ttl = 0; rport_present = add_rport; rport = 0; } void t_via::add_extension(const t_parameter &p) { extensions.push_back(p); } string t_via::encode(void) const { string s; s = protocol_name + '/' + protocol_version + '/' + transport; s += ' '; s += host; if (port > 0) { s += ':'; s += int2str(port); } if (ttl > 0) s += int2str(ttl, ";ttl=%d"); if (maddr.size() > 0) { s += ";maddr="; s += maddr; } if (received.size() > 0) { s += ";received="; s += received; } if (rport_present) { s += ";rport"; if (rport > 0) { s += "="; s += int2str(rport); } } if (branch.size() > 0) { s += ";branch="; s += branch; } s += param_list2str(extensions); return s; } void t_via::get_response_dst(t_ip_port &ip_port) const { string url_str("sip:"); // RFC 3261 18.2.2 // Determine the address to send a repsonse to // NOTE: the received-parameter will be added by the listener if needed. if (tolower(transport) == "tcp") { // NOTE: The response must be sent over the connection on which // the request was received. The address returned here is an // alternative if that connection is closed already. if (received.size() > 0) { url_str += received; } else { url_str += host; } url_str += ":"; // NOTE: The rport parameter is not processed here as it only // applies to unreliable transports (RFC 3581 4) url_str += int2str(port); t_url u(url_str); list ip_list = u.get_h_ip_srv("tcp"); ip_port = ip_list.front(); } else { if (maddr.size() > 0) { url_str += maddr; } else if (received.size() > 0) { url_str += received; } else { url_str += host; } // RFC 3581 4 if (rport_present && rport > 0) { // NOTE: the rport value will be added by the UDP listener // if the rport parameter without value was present. url_str += ':'; url_str += int2str(rport); } else if (port != 0) { url_str += ':'; url_str += int2str(port); } // If there was no maddr parameter, then the URL will always point to // an IP address; either the host was an IP address or a received parameter // containing an IP address was added (see RFC 3261 18.2.1) // If there was an maddr, then the URL can be a domain that could have // multiple SRV records. RFC 3263 section 5 does not specify what to do in // this case. So just send the response to the first destination. t_url u(url_str); list ip_list = u.get_h_ip_srv("udp"); ip_port = ip_list.front(); } } bool t_via::rfc3261_compliant(void) const { return (branch.find(RFC3261_COOKIE) == 0); } t_hdr_via::t_hdr_via() : t_header("Via", "v") {} void t_hdr_via::add_via(const t_via &v) { populated = true; via_list.push_back(v); } string t_hdr_via::encode(void) const { return (t_parser::multi_values_as_list ? t_header::encode() : encode_multi_header()); } string t_hdr_via::encode_multi_header(void) const { string s; if (!populated) return s; for (list::const_iterator i = via_list.begin(); i != via_list.end(); i++) { s += (t_parser::compact_headers ? compact_name : header_name); s += ": "; s += i->encode(); s += CRLF; } return s; } string t_hdr_via::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = via_list.begin(); i != via_list.end(); i++) { if (i != via_list.begin()) s += ","; s += i->encode(); } return s; } void t_hdr_via::get_response_dst(t_ip_port &ip_port) const { if (!populated) { ip_port.clear(); return; } via_list.front().get_response_dst(ip_port); } twinkle-1.10.1/src/parser/hdr_via.h000066400000000000000000000034271277565361200171430ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Via header #ifndef _H_HDR_VIA #define _H_HDR_VIA #include #include #include "header.h" #include "parameter.h" class t_via { public: string protocol_name; string protocol_version; string transport; string host; int port; int ttl; string maddr; string received; string branch; // RFC 3581: symetric response routing bool rport_present; int rport; list extensions; t_via(); t_via(const string &_host, const int _port, bool add_rport = true); void add_extension(const t_parameter &p); string encode(void) const; // Get the response destination void get_response_dst(t_ip_port &ip_port) const; // Returns true if branch starts with RFC 3261 magic cookie bool rfc3261_compliant(void) const; }; class t_hdr_via : public t_header { public: list via_list; t_hdr_via(); void add_via(const t_via &v); string encode(void) const; string encode_multi_header(void) const; string encode_value(void) const; // Get the response destination void get_response_dst(t_ip_port &ip_port) const; }; #endif twinkle-1.10.1/src/parser/hdr_warning.cpp000066400000000000000000000041571277565361200203650ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "hdr_warning.h" #include "util.h" t_warning::t_warning() { code = 0; port = 0; } t_warning::t_warning(const string &_host, int _port, int _code, string _text) { host = _host; port = _port; code = _code; switch(code) { case 300: text = WARNING_300; break; case 301: text = WARNING_301; break; case 302: text = WARNING_302; break; case 303: text = WARNING_303; break; case 304: text = WARNING_304; break; case 305: text = WARNING_305; break; case 306: text = WARNING_306; break; case 307: text = WARNING_307; break; case 330: text = WARNING_330; break; case 331: text = WARNING_331; break; case 370: text = WARNING_370; break; case 399: text = WARNING_399; break; default: text = "Warning"; } if (_text != "") { text += ": "; text += _text; } } string t_warning::encode(void) const { string s; s = int2str(code, "%3d"); s += ' '; s += host; if (port > 0) s += int2str(port, ":%d"); s += ' '; s += '"'; s += text; s += '"'; return s; } t_hdr_warning::t_hdr_warning() : t_header("Warning") {} void t_hdr_warning::add_warning(const t_warning &w) { populated = true; warnings.push_back(w); } string t_hdr_warning::encode_value(void) const { string s; if (!populated) return s; for (list::const_iterator i = warnings.begin(); i != warnings.end(); i++) { if (i != warnings.begin()) s += ", "; s += i->encode(); } return s; } twinkle-1.10.1/src/parser/hdr_warning.h000066400000000000000000000046441277565361200200330ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Warning header #ifndef _H_HDR_WARNING #define _H_HDR_WARNING #include #include #include "header.h" // Warning codes #define W_300_INCOMPATIBLE_NWK_PROT 300 #define W_301_INCOMPATIBLE_ADDR_FORMAT 301 #define W_302_INCOMPATIBLE_TRANS_PROT 302 #define W_303_INCOMPATIBLE_BW_UNITS 303 #define W_304_MEDIA_TYPE_NOT_AVAILABLE 304 #define W_305_INCOMPATIBLE_MEDIA_FORMAT 305 #define W_306_ATTRIBUTE_NOT_UNDERSTOOD 306 #define W_307_PARAMETER_NOT_UNDERSTOOD 307 #define W_330_MULTICAST_NOT_AVAILABLE 330 #define W_331_UNICAST_NOT_AVAILABLE 331 #define W_370_INSUFFICIENT_BANDWITH 370 #define W_399_MISCELLANEOUS 399 // Warning texts #define WARNING_300 "Incompatible network protocol" #define WARNING_301 "Incompatible network address formats" #define WARNING_302 "Incompatible transport protocol" #define WARNING_303 "Incompatible bandwidth units" #define WARNING_304 "Media type not available" #define WARNING_305 "Incompatible media format" #define WARNING_306 "Attribute not understood" #define WARNING_307 "Session description parameter not understood" #define WARNING_330 "Multicast not available" #define WARNING_331 "Unicast not available" #define WARNING_370 "Insufficient bandwidth" #define WARNING_399 "Miscellanous warning" using namespace std; class t_warning { public: int code; string host; int port; string text; t_warning(); // The default text will be used as warning appended with passed // text if present t_warning(const string &_host, int _port, int _code, string _text); string encode(void) const; }; class t_hdr_warning : public t_header { public: list warnings; t_hdr_warning(); void add_warning(const t_warning &w); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/hdr_www_authenticate.cpp000066400000000000000000000021111277565361200222660ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "hdr_www_authenticate.h" #include "definitions.h" t_hdr_www_authenticate::t_hdr_www_authenticate() : t_header("WWW-Authenticate") {} void t_hdr_www_authenticate::set_challenge(const t_challenge &c) { populated = true; challenge = c; } string t_hdr_www_authenticate::encode_value(void) const { if (!populated) return ""; return challenge.encode(); } twinkle-1.10.1/src/parser/hdr_www_authenticate.h000066400000000000000000000021431277565361200217400ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // WWW-Authenticate header #ifndef _HDR_WWW_AUTHENTICATE_H #define _HDR_WWW_AUTHENTICATE_H #include #include #include "challenge.h" #include "header.h" using namespace std; class t_hdr_www_authenticate : public t_header { public: t_challenge challenge; t_hdr_www_authenticate(); void set_challenge(const t_challenge &c); string encode_value(void) const; }; #endif twinkle-1.10.1/src/parser/header.cpp000066400000000000000000000035071277565361200173110ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "header.h" #include "parse_ctrl.h" #include "protocol.h" #include "util.h" t_header::t_header() : populated(false) {} t_header::t_header(const string &_header_name, const string &_compact_name) : populated(false), header_name(_header_name), compact_name(_compact_name) {} string t_header::encode(void) const { string s; if (!populated) return s; s = (t_parser::compact_headers && !compact_name.empty() ? compact_name : header_name); s += ": "; s += encode_value(); s += CRLF; return s; } string t_header::encode_env(void) const { string s("SIP_"); s += toupper(replace_char(header_name, '-', '_')); s += '='; s += encode_value(); return s; } bool t_header::is_populated() const { return populated; } string t_header::get_name(void) const { return header_name; } string t_header::get_value(void) const { string s; string::size_type i; if (!populated) return s; s = encode(); i = s.find(':'); // The colon cannot be the first or last character if (i == string::npos || i == s.size()-1) return ""; s = s.substr(i+1); i = s.find(CRLF); s = s.substr(0, i); return (trim(s)); } twinkle-1.10.1/src/parser/header.h000066400000000000000000000035501277565361200167540ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Base class for message and response headers #ifndef _HEADER_H #define _HEADER_H #include using namespace std; class t_header { private: t_header(); protected: bool populated; // true = header is populated string header_name; // Full name of header in SIP messages string compact_name; // Compact name of header in SIP messages public: virtual ~t_header() {} t_header(const string &_header_name, const string &_compact_name = ""); // Return the text encoded header (CRLF at end of string) virtual string encode(void) const; // Return the text encoded value part (no CRLF at end of string) virtual string encode_value(void) const = 0; // Return a environemnt variable setting // The format of the setting is: // // SIP_
= // // The header name is in capitals. Dashes are replaced by underscores. virtual string encode_env(void) const; // Get the header name string get_name(void) const; // Get text encoding of the header value only. // I.e. without header name and no trailing CRLF string get_value(void) const; // Return true if the header is populated bool is_populated(void) const; }; #endif twinkle-1.10.1/src/parser/identity.cpp000066400000000000000000000022711277565361200177070ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "identity.h" #include "util.h" t_identity::t_identity() : display(), uri() {} void t_identity::set_display(const string &d) { display = d; } void t_identity::set_uri(const string &u) { uri.set_url(u); } void t_identity::set_uri(const t_url &u) { uri = u; } string t_identity::encode(void) const { string s; if (display.size() > 0) { s += '"'; s += escape(display, '"'); s += '"'; s += ' '; } s += '<'; s += uri.encode(); s += '>'; return s; } twinkle-1.10.1/src/parser/identity.h000066400000000000000000000020541277565361200173530ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _IDENTITY_H #define _IDENTITY_H #include #include "sockets/url.h" using namespace std; class t_identity { public: string display; // display name t_url uri; t_identity(); void set_display(const string &d); void set_uri(const string &u); void set_uri(const t_url &u); string encode(void) const; }; #endif twinkle-1.10.1/src/parser/media_type.cpp000066400000000000000000000041501277565361200201740ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include "media_type.h" #include "util.h" #include "utils/mime_database.h" using namespace std; using namespace utils; t_media::t_media() : q(1.0) {} t_media::t_media(const string &t, const string &s) : type(t), subtype(s), q(1.0) {} t_media::t_media(const string &mime_type) : q(1.0) { vector v = split(mime_type, '/'); if (v.size() == 2) { type = v[0]; subtype = v[1]; } } void t_media::add_params(const list &l) { list::const_iterator i = l.begin(); media_param_list.clear(); accept_extension_list.clear(); // Add media parameters while (i != l.end() && i->name != "q") { if (i->name == "charset") { charset = i->value; } else { media_param_list.push_back(*i); } ++i; } // Set the quality factor if (i != l.end()) { q = atof(i->value.c_str()); i++; } // Add accept extension parameters while (i != l.end()) { accept_extension_list.push_back(*i); i++; } } string t_media::encode(void) const { string s; s = type + '/' + subtype; if (!charset.empty()) { s += ";charset="; s += charset; } s += param_list2str(media_param_list); if (q != 1) { s += ";q="; s += float2str(q, 1); } s += param_list2str(accept_extension_list); return s; } string t_media::get_file_glob(void) const { string file_glob = mime_database->get_glob(type + '/' + subtype); return file_glob; } twinkle-1.10.1/src/parser/media_type.h000066400000000000000000000042341277565361200176440ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Media MIME type definition. */ #ifndef _MEDIA_TYPE_H #define _MEDIA_TYPE_H #include #include #include "parameter.h" using namespace std; /** Media MIME type definition. */ class t_media { public: string type; /**< main type */ string subtype; /**< subtype */ string charset; /**< Character set */ float q; /**< quality factor */ list media_param_list; /**< media paramters */ list accept_extension_list; /**< accept parameters */ /** Constructor */ t_media(); /** * Constructor. * Construct object with a specic type and subtype. * @param t [in] type * @param s [in] subtype */ t_media(const string &t, const string &s); /** * Constructor. * Construct a media object from a mime type name * @param mime_type [in] The mime type name, e.g. "text/plain" */ t_media(const string &mime_type); /** * Add a parameter list. * Method for parser to add the parsed parameter list l. * l should start with optional media parameters followed * by the q-paramter followed by accept parameters. * @param l [in] The parameter list. */ void add_params(const list &l); /** * Encode as string. * @return The encoded media type. */ string encode(void) const; /** * Get the glob for a file name containing this MIME type. * E.g. .txt for text/plain * @return The file name extension. */ string get_file_glob(void) const; }; #endif twinkle-1.10.1/src/parser/milenage.cpp000066400000000000000000000161351277565361200176430ustar00rootroot00000000000000/*------------------------------------------------------------------- * Example algorithms f1, f1*, f2, f3, f4, f5, f5* *------------------------------------------------------------------- * * A sample implementation of the example 3GPP authentication and * key agreement functions f1, f1*, f2, f3, f4, f5 and f5*. This is * a byte-oriented implementation of the functions, and of the block * cipher kernel function Rijndael. * * This has been coded for clarity, not necessarily for efficiency. * * The functions f2, f3, f4 and f5 share the same inputs and have * been coded together as a single function. f1, f1* and f5* are * all coded separately. * *-----------------------------------------------------------------*/ #include "milenage.h" #include "rijndael.h" /*--------------------------- prototypes --------------------------*/ /*------------------------------------------------------------------- * Algorithm f1 *------------------------------------------------------------------- * * Computes network authentication code MAC-A from key K, random * challenge RAND, sequence number SQN and authentication management * field AMF. * *-----------------------------------------------------------------*/ void f1 ( u8 k[16], u8 rand[16], u8 sqn[6], u8 amf[2], u8 mac_a[8], u8 op[16] ) { u8 op_c[16]; u8 temp[16]; u8 in1[16]; u8 out1[16]; u8 rijndaelInput[16]; u8 i; RijndaelKeySchedule( k ); ComputeOPc( op_c, op ); for (i=0; i<16; i++) rijndaelInput[i] = rand[i] ^ op_c[i]; RijndaelEncrypt( rijndaelInput, temp ); for (i=0; i<6; i++) { in1[i] = sqn[i]; in1[i+8] = sqn[i]; } for (i=0; i<2; i++) { in1[i+6] = amf[i]; in1[i+14] = amf[i]; } /* XOR op_c and in1, rotate by r1=64, and XOR * * on the constant c1 (which is all zeroes) */ for (i=0; i<16; i++) rijndaelInput[(i+8) % 16] = in1[i] ^ op_c[i]; /* XOR on the value temp computed before */ for (i=0; i<16; i++) rijndaelInput[i] ^= temp[i]; RijndaelEncrypt( rijndaelInput, out1 ); for (i=0; i<16; i++) out1[i] ^= op_c[i]; for (i=0; i<8; i++) mac_a[i] = out1[i]; return; } /* end of function f1 */ /*------------------------------------------------------------------- * Algorithms f2-f5 *------------------------------------------------------------------- * * Takes key K and random challenge RAND, and returns response RES, * confidentiality key CK, integrity key IK and anonymity key AK. * *-----------------------------------------------------------------*/ void f2345 ( u8 k[16], u8 rand[16], u8 res[8], u8 ck[16], u8 ik[16], u8 ak[6], u8 op[16] ) { u8 op_c[16]; u8 temp[16]; u8 out[16]; u8 rijndaelInput[16]; u8 i; RijndaelKeySchedule( k ); ComputeOPc( op_c, op ); for (i=0; i<16; i++) rijndaelInput[i] = rand[i] ^ op_c[i]; RijndaelEncrypt( rijndaelInput, temp ); /* To obtain output block OUT2: XOR OPc and TEMP, * * rotate by r2=0, and XOR on the constant c2 (which * * is all zeroes except that the last bit is 1). */ for (i=0; i<16; i++) rijndaelInput[i] = temp[i] ^ op_c[i]; rijndaelInput[15] ^= 1; RijndaelEncrypt( rijndaelInput, out ); for (i=0; i<16; i++) out[i] ^= op_c[i]; for (i=0; i<8; i++) res[i] = out[i+8]; for (i=0; i<6; i++) ak[i] = out[i]; /* To obtain output block OUT3: XOR OPc and TEMP, * * rotate by r3=32, and XOR on the constant c3 (which * * is all zeroes except that the next to last bit is 1). */ for (i=0; i<16; i++) rijndaelInput[(i+12) % 16] = temp[i] ^ op_c[i]; rijndaelInput[15] ^= 2; RijndaelEncrypt( rijndaelInput, out ); for (i=0; i<16; i++) out[i] ^= op_c[i]; for (i=0; i<16; i++) ck[i] = out[i]; /* To obtain output block OUT4: XOR OPc and TEMP, * * rotate by r4=64, and XOR on the constant c4 (which * * is all zeroes except that the 2nd from last bit is 1). */ for (i=0; i<16; i++) rijndaelInput[(i+8) % 16] = temp[i] ^ op_c[i]; rijndaelInput[15] ^= 4; RijndaelEncrypt( rijndaelInput, out ); for (i=0; i<16; i++) out[i] ^= op_c[i]; for (i=0; i<16; i++) ik[i] = out[i]; return; } /* end of function f2345 */ /*------------------------------------------------------------------- * Algorithm f1* *------------------------------------------------------------------- * * Computes resynch authentication code MAC-S from key K, random * challenge RAND, sequence number SQN and authentication management * field AMF. * *-----------------------------------------------------------------*/ void f1star( u8 k[16], u8 rand[16], u8 sqn[6], u8 amf[2], u8 mac_s[8], u8 op[16] ) { u8 op_c[16]; u8 temp[16]; u8 in1[16]; u8 out1[16]; u8 rijndaelInput[16]; u8 i; RijndaelKeySchedule( k ); ComputeOPc( op_c, op ); for (i=0; i<16; i++) rijndaelInput[i] = rand[i] ^ op_c[i]; RijndaelEncrypt( rijndaelInput, temp ); for (i=0; i<6; i++) { in1[i] = sqn[i]; in1[i+8] = sqn[i]; } for (i=0; i<2; i++) { in1[i+6] = amf[i]; in1[i+14] = amf[i]; } /* XOR op_c and in1, rotate by r1=64, and XOR * * on the constant c1 (which is all zeroes) */ for (i=0; i<16; i++) rijndaelInput[(i+8) % 16] = in1[i] ^ op_c[i]; /* XOR on the value temp computed before */ for (i=0; i<16; i++) rijndaelInput[i] ^= temp[i]; RijndaelEncrypt( rijndaelInput, out1 ); for (i=0; i<16; i++) out1[i] ^= op_c[i]; for (i=0; i<8; i++) mac_s[i] = out1[i+8]; return; } /* end of function f1star */ /*------------------------------------------------------------------- * Algorithm f5* *------------------------------------------------------------------- * * Takes key K and random challenge RAND, and returns resynch * anonymity key AK. * *-----------------------------------------------------------------*/ void f5star( u8 k[16], u8 rand[16], u8 ak[6], u8 op[16] ) { u8 op_c[16]; u8 temp[16]; u8 out[16]; u8 rijndaelInput[16]; u8 i; RijndaelKeySchedule( k ); ComputeOPc( op_c, op ); for (i=0; i<16; i++) rijndaelInput[i] = rand[i] ^ op_c[i]; RijndaelEncrypt( rijndaelInput, temp ); /* To obtain output block OUT5: XOR OPc and TEMP, * * rotate by r5=96, and XOR on the constant c5 (which * * is all zeroes except that the 3rd from last bit is 1). */ for (i=0; i<16; i++) rijndaelInput[(i+4) % 16] = temp[i] ^ op_c[i]; rijndaelInput[15] ^= 8; RijndaelEncrypt( rijndaelInput, out ); for (i=0; i<16; i++) out[i] ^= op_c[i]; for (i=0; i<6; i++) ak[i] = out[i]; return; } /* end of function f5star */ /*------------------------------------------------------------------- * Function to compute OPc from OP and K. Assumes key schedule has already been performed. *-----------------------------------------------------------------*/ void ComputeOPc( u8 op_c[16], u8 op[16] ) { u8 i; RijndaelEncrypt( op, op_c ); for (i=0; i<16; i++) op_c[i] ^= op[i]; return; } /* end of function ComputeOPc */ twinkle-1.10.1/src/parser/milenage.h000066400000000000000000000023361277565361200173060ustar00rootroot00000000000000/*------------------------------------------------------------------- * Example algorithms f1, f1*, f2, f3, f4, f5, f5* *------------------------------------------------------------------- * * A sample implementation of the example 3GPP authentication and * key agreement functions f1, f1*, f2, f3, f4, f5 and f5*. This is * a byte-oriented implementation of the functions, and of the block * cipher kernel function Rijndael. * * This has been coded for clarity, not necessarily for efficiency. * * The functions f2, f3, f4 and f5 share the same inputs and have * been coded together as a single function. f1, f1* and f5* are * all coded separately. * *-----------------------------------------------------------------*/ #ifndef MILENAGE_H #define MILENAGE_H typedef unsigned char u8; void f1 ( u8 k[16], u8 rand[16], u8 sqn[6], u8 amf[2], u8 mac_a[8], u8 op[16] ); void f2345 ( u8 k[16], u8 rand[16], u8 res[8], u8 ck[16], u8 ik[16], u8 ak[6], u8 op[16] ); void f1star( u8 k[16], u8 rand[16], u8 sqn[6], u8 amf[2], u8 mac_s[8], u8 op[16] ); void f5star( u8 k[16], u8 rand[16], u8 ak[6], u8 op[16] ); void ComputeOPc( u8 op_c[16], u8 op[16] ); #endif twinkle-1.10.1/src/parser/parameter.cpp000066400000000000000000000036641277565361200200450ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "parameter.h" #include "util.h" t_parameter::t_parameter() { type = VALUE; } t_parameter::t_parameter(const string &n) { type = NOVALUE; name = n; } t_parameter::t_parameter(const string &n, const string &v) { type = VALUE; name = n; value = v; } string t_parameter::encode(void) const { string s; s += name; if (type == VALUE) { s += '='; if (must_quote(value)) { s += '\"' + value + '\"'; } else { s += value; } } return s; } bool t_parameter::operator==(const t_parameter &rhs) { return (type == rhs.type && name == rhs.name); } t_parameter str2param(const string &s) { vector l = split_on_first(s, '='); if (l.size() == 1) { return t_parameter(s); } else { return t_parameter(trim(l[0]), trim(l[1])); } } string param_list2str(const list &l) { string s; for (list::const_iterator i = l.begin(); i != l.end(); i++) { s += ';'; s += i->encode(); } return s; } list str2param_list(const string &s) { list result; vector l = split(s, ';'); for (vector::const_iterator i = l.begin(); i != l.end(); i++) { t_parameter p = str2param(trim(*i)); result.push_back(p); } return result; } twinkle-1.10.1/src/parser/parameter.h000066400000000000000000000031111277565361200174750ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _PARAMETER_H #define _PARAMETER_H #include #include using namespace std; class t_parameter { public: enum t_param_type{ NOVALUE, // a parameter without a value VALUE // parameter having a value (default) }; t_param_type type; // type of parameter string name; // name of parameter string value; // value of parameter if type is VALUE t_parameter(); // Construct a NOVALUE parameter with name = n t_parameter(const string &n); // Construct a VALUE parameter with name = n, value = v t_parameter(const string &n, const string &v); string encode(void) const; bool operator==(const t_parameter &rhs); }; // Decode a parameter t_parameter str2param(const string &s); // Encode a parameter list string param_list2str(const list &l); // Decode a parameter list list str2param_list(const string &s); #endif twinkle-1.10.1/src/parser/parse_ctrl.cpp000066400000000000000000000062601277565361200202160ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "parse_ctrl.h" #include "protocol.h" #include "util.h" #include "audits/memman.h" // Interface to Bison extern int yyparse(void); // Interface to Flex struct yy_buffer_state; extern struct yy_buffer_state *yy_scan_string(const char *); extern void yy_delete_buffer(struct yy_buffer_state *); t_mutex t_parser::mtx_parser; bool t_parser::check_max_forwards = true; bool t_parser::compact_headers = false; bool t_parser::multi_values_as_list = true; int t_parser::comment_level = 0; list t_parser::parse_errors; string t_parser::unfold(const string &h) { string::size_type i; string s = h; while ((i = s.find("\r\n ")) != string::npos) { s.replace(i, 3, " "); } while ((i = s.find("\r\n\t")) != string::npos) { s.replace(i, 3, " "); } // This is only for easy testing of hand edited messages // in Linux where the end of line character is \n only. while ((i = s.find("\n ")) != string::npos) { s.replace(i, 2, " "); } while ((i = s.find("\n\t")) != string::npos) { s.replace(i, 2, " "); } return s; } t_parser::t_context t_parser::context = t_parser::X_INITIAL; t_sip_message *t_parser::msg = NULL; t_sip_message *t_parser::parse(const string &s, list &parse_errors_) { t_mutex_guard guard(mtx_parser); int ret; struct yy_buffer_state *b; msg = NULL; parse_errors.clear(); string x = unfold(s); b = yy_scan_string(x.c_str()); ret = yyparse(); yy_delete_buffer(b); if (ret != 0) { if (msg) { MEMMAN_DELETE(msg); delete msg; msg = NULL; } throw ret; } parse_errors_ = parse_errors; return msg; } t_sip_message *t_parser::parse_headers(const string &s, list &parse_errors_) { string msg("INVITE sip:fake@fake.invalid SIP/2.0"); msg += CRLF; list hdr_list = str2param_list(s); for (list::iterator i = hdr_list.begin(); i != hdr_list.end(); i++) { msg += unescape_hex(i->name); msg += ": "; msg += unescape_hex(i->value); msg += CRLF; } msg += CRLF; return parse(msg, parse_errors_); } void t_parser::enter_ctx_comment(void) { comment_level = 0; context = t_parser::X_COMMENT; } void t_parser::inc_comment_level(void) { comment_level++; } bool t_parser::dec_comment_level(void) { if (comment_level == 0) return false; comment_level--; return true; } void t_parser::add_header_error(const string &header_name) { string s = "Parse error in header: " + header_name; parse_errors.push_back(s); } t_syntax_error::t_syntax_error(const string &e) { error = e; } twinkle-1.10.1/src/parser/parse_ctrl.h000066400000000000000000000105441277565361200176630ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Parser control #ifndef _PARSE_CTRL_H #define _PARSE_CTRL_H #include "sip_message.h" #include "threads/mutex.h" #define MSG t_parser::msg #define CTXT_INITIAL (t_parser::context = t_parser::X_INITIAL) #define CTXT_URI (t_parser::context = t_parser::X_URI) #define CTXT_URI_SPECIAL (t_parser::context = t_parser::X_URI_SPECIAL) #define CTXT_LANG (t_parser::context = t_parser::X_LANG) #define CTXT_WORD (t_parser::context = t_parser::X_WORD) #define CTXT_NUM (t_parser::context = t_parser::X_NUM) #define CTXT_DATE (t_parser::context = t_parser::X_DATE) #define CTXT_LINE (t_parser::context = t_parser::X_LINE) #define CTXT_COMMENT (t_parser::enter_ctx_comment()) #define CTXT_NEW (t_parser::context = t_parser::X_NEW) #define CTXT_AUTH_SCHEME (t_parser::context = t_parser::X_AUTH_SCHEME) #define CTXT_IPV6ADDR (t_parser::context = t_parser::X_IPV6ADDR) #define CTXT_PARAMVAL (t_parser::context = t_parser::X_PARAMVAL) #define PARSE_ERROR(h) { t_parser::add_header_error(h); CTXT_INITIAL; } // The t_parser controls the direction of the scanner/parser // process and it stores the results from the parser. class t_parser { private: /** Mutex to synchronize parse operations */ static t_mutex mtx_parser; // Level for nested comments static int comment_level; // Non-fatal parse errors generated during parsing. static list parse_errors; // Unfold SIP headers static string unfold(const string &h); public: enum t_context { X_INITIAL, // Initial context X_URI, // URI context where parameters belong to URI X_URI_SPECIAL, // URI context where parameters belong to SIP header // if URI is not enclosed by < and > X_LANG, // Language tag context X_WORD, // Word context X_NUM, // Number context X_DATE, // Date context X_LINE, // Whole line context X_COMMENT, // Comment context X_NEW, // Start of a new SIP message to distinguish // request from responses X_AUTH_SCHEME, // Authorization scheme context X_IPV6ADDR, // IPv6 address context X_PARAMVAL, // Generic parameter value context }; // Parser options // According to RFC3261 the Max-Forwards header is mandatory, but // many implementations do not send this header. static bool check_max_forwards; // Encode headers in compact forom static bool compact_headers; // Encode multiple values as comma separated list or multiple headers static bool multi_values_as_list; static t_context context; // Scan context static t_sip_message *msg; // Message that has been parsed /** * Parse a string representing a SIP message. * @param s [in] String to parse. * @param parse_errors_ [out] List of non-fatal parse errors. * @return The parsed SIP message. * @throw int exception when parsing fails. */ static t_sip_message *parse(const string &s, list &parse_errors_); /** * Parse a string of headers (hdr1=val1;hdr=val2;...) * The resulting SIP message is a SIP request with a fake request line. * @param s [in] String to parse. * @param parse_errors_ [out] List of non-fatal parse errors. * @return The parsed SIP message. * @throw int exception when parsing fails. */ static t_sip_message *parse_headers(const string &s, list &parse_errors_); static void enter_ctx_comment(void); // Increment and decrement levels for nested comments // dec_comment_level returns false if the level cannot be decremented. static void inc_comment_level(void); static bool dec_comment_level(void); // Add parsing error for a header to the list of parse errors static void add_header_error(const string &header_name); }; // Error that can be thrown as exception class t_syntax_error { public: string error; t_syntax_error(const string &e); }; #endif twinkle-1.10.1/src/parser/parser.yxx000066400000000000000000001267621277565361200174340ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ %{ #include #include #include #include "media_type.h" #include "parameter.h" #include "parse_ctrl.h" #include "request.h" #include "response.h" #include "util.h" #include "audits/memman.h" using namespace std; extern int yylex(void); void yyerror(const char *s); %} // The %debug option causes a problem with the %destructor options later on. // The bison compilor generates undefined symbols: // // parser.y: In function `void yysymprint(FILE*, int, yystype)': // parser.y:0: error: `null' undeclared (first use this function) // // So if you need to debug, then outcomment the %destructor first. This will // do no harm to your debugging, it only will cause memory leaks during // error handling. // // %debug %union { int yyt_int; ulong yyt_ulong; float yyt_float; string *yyt_str; t_parameter *yyt_param; list *yyt_params; t_media *yyt_media; t_coding *yyt_coding; t_language *yyt_language; t_alert_param *yyt_alert_param; t_info_param *yyt_info_param; list *yyt_contacts; t_contact_param *yyt_contact; t_error_param *yyt_error_param; t_identity *yyt_from_addr; t_route *yyt_route; t_server *yyt_server; t_via *yyt_via; t_warning *yyt_warning; t_digest_response *yyt_dig_resp; t_credentials *yyt_credentials; t_digest_challenge *yyt_dig_chlg; t_challenge *yyt_challenge; } %token T_NUM %token T_TOKEN %token T_QSTRING %token T_COMMENT %token T_LINE %token T_URI %token T_URI_WILDCARD %token T_DISPLAY %token T_LANG %token T_WORD %token T_WKDAY %token T_MONTH %token T_GMT %token T_SIP %token T_METHOD %token T_AUTH_DIGEST %token T_AUTH_OTHER %token T_IPV6ADDR %token T_PARAMVAL %token T_HDR_ACCEPT %token T_HDR_ACCEPT_ENCODING %token T_HDR_ACCEPT_LANGUAGE %token T_HDR_ALERT_INFO %token T_HDR_ALLOW %token T_HDR_ALLOW_EVENTS %token T_HDR_AUTHENTICATION_INFO %token T_HDR_AUTHORIZATION %token T_HDR_CALL_ID %token T_HDR_CALL_INFO %token T_HDR_CONTACT %token T_HDR_CONTENT_DISP %token T_HDR_CONTENT_ENCODING %token T_HDR_CONTENT_LANGUAGE %token T_HDR_CONTENT_LENGTH %token T_HDR_CONTENT_TYPE %token T_HDR_CSEQ %token T_HDR_DATE %token T_HDR_ERROR_INFO %token T_HDR_EVENT %token T_HDR_EXPIRES %token T_HDR_FROM %token T_HDR_IN_REPLY_TO %token T_HDR_MAX_FORWARDS %token T_HDR_MIN_EXPIRES %token T_HDR_MIME_VERSION %token T_HDR_ORGANIZATION %token T_HDR_P_ASSERTED_IDENTITY %token T_HDR_P_PREFERRED_IDENTITY %token T_HDR_PRIORITY %token T_HDR_PRIVACY %token T_HDR_PROXY_AUTHENTICATE %token T_HDR_PROXY_AUTHORIZATION %token T_HDR_PROXY_REQUIRE %token T_HDR_RACK %token T_HDR_RECORD_ROUTE %token T_HDR_SERVICE_ROUTE %token T_HDR_REFER_SUB %token T_HDR_REFER_TO %token T_HDR_REFERRED_BY %token T_HDR_REPLACES %token T_HDR_REPLY_TO %token T_HDR_REQUIRE %token T_HDR_REQUEST_DISPOSITION %token T_HDR_RETRY_AFTER %token T_HDR_ROUTE %token T_HDR_RSEQ %token T_HDR_SERVER %token T_HDR_SIP_ETAG %token T_HDR_SIP_IF_MATCH %token T_HDR_SUBJECT %token T_HDR_SUBSCRIPTION_STATE %token T_HDR_SUPPORTED %token T_HDR_TIMESTAMP %token T_HDR_TO %token T_HDR_UNSUPPORTED %token T_HDR_USER_AGENT %token T_HDR_VIA %token T_HDR_WARNING %token T_HDR_WWW_AUTHENTICATE %token T_HDR_UNKNOWN %token T_CRLF %token T_ERROR // The token T_NULL is never returned by the scanner. %token T_NULL %destructor { MEMMAN_DELETE($$); delete $$; } T_TOKEN; %destructor { MEMMAN_DELETE($$); delete $$; } T_QSTRING %destructor { MEMMAN_DELETE($$); delete $$; } T_COMMENT %destructor { MEMMAN_DELETE($$); delete $$; } T_LINE %destructor { MEMMAN_DELETE($$); delete $$; } T_URI %destructor { MEMMAN_DELETE($$); delete $$; } T_DISPLAY %destructor { MEMMAN_DELETE($$); delete $$; } T_LANG %destructor { MEMMAN_DELETE($$); delete $$; } T_WORD %destructor { MEMMAN_DELETE($$); delete $$; } T_METHOD %destructor { MEMMAN_DELETE($$); delete $$; } T_AUTH_OTHER %destructor { MEMMAN_DELETE($$); delete $$; } T_IPV6ADDR %destructor { MEMMAN_DELETE($$); delete $$; } T_PARAMVAL %destructor { MEMMAN_DELETE($$); delete $$; } T_HDR_UNKNOWN %type alert_param %type auth_params %type call_id %type challenge %type comment %type contact_addr %type contact_param %type contacts %type content_coding %type credentials %type delay %type digest_challenge %type digest_response %type display_name %type error_param %type from_addr %type hdr_unknown %type host %type ipv6reference %type info_param %type language %type media_range %type parameter %type parameter_val %type parameters %type q_factor %type rec_route %type sent_protocol %type server %type sip_version %type timestamp %type via_parm %type warning %destructor { MEMMAN_DELETE($$); delete $$; } alert_param %destructor { MEMMAN_DELETE($$); delete $$; } auth_params %destructor { MEMMAN_DELETE($$); delete $$; } call_id %destructor { MEMMAN_DELETE($$); delete $$; } challenge %destructor { MEMMAN_DELETE($$); delete $$; } comment %destructor { MEMMAN_DELETE($$); delete $$; } contact_addr %destructor { MEMMAN_DELETE($$); delete $$; } contact_param %destructor { MEMMAN_DELETE($$); delete $$; } contacts %destructor { MEMMAN_DELETE($$); delete $$; } content_coding %destructor { MEMMAN_DELETE($$); delete $$; } credentials %destructor { MEMMAN_DELETE($$); delete $$; } digest_challenge %destructor { MEMMAN_DELETE($$); delete $$; } digest_response %destructor { MEMMAN_DELETE($$); delete $$; } display_name %destructor { MEMMAN_DELETE($$); delete $$; } error_param %destructor { MEMMAN_DELETE($$); delete $$; } from_addr %destructor { MEMMAN_DELETE($$); delete $$; } hdr_unknown %destructor { MEMMAN_DELETE($$); delete $$; } host %destructor { MEMMAN_DELETE($$); delete $$; } ipv6reference %destructor { MEMMAN_DELETE($$); delete $$; } info_param %destructor { MEMMAN_DELETE($$); delete $$; } language %destructor { MEMMAN_DELETE($$); delete $$; } media_range %destructor { MEMMAN_DELETE($$); delete $$; } parameter %destructor { MEMMAN_DELETE($$); delete $$; } parameter_val %destructor { MEMMAN_DELETE($$); delete $$; } parameters %destructor { MEMMAN_DELETE($$); delete $$; } rec_route %destructor { MEMMAN_DELETE($$); delete $$; } sent_protocol %destructor { MEMMAN_DELETE($$); delete $$; } server %destructor { MEMMAN_DELETE($$); delete $$; } sip_version %destructor { MEMMAN_DELETE($$); delete $$; } via_parm %destructor { MEMMAN_DELETE($$); delete $$; } warning %% sip_message: { CTXT_NEW; } sip_message2 ; sip_message2: request | response | error T_NULL { /* KLUDGE to work around a memory leak in bison. * T_NULL does never match, so the parser never * gets here. The error keyword causes bison * to eat all input and destroy all tokens returned * by the parser. * Without this workaround the following input causes * the parser to leak: * * INVITE INVITE .... * * In request_line a T_METHOD is returned as look ahead * token when bison tries to match sip_version. * This does not match, but the look ahead token is * never destructed by Bison. */ YYABORT; } ; request: request_line headers T_CRLF { /* Parsing stops here. Remaining text is * not parsed. */ YYACCEPT; } ; // KLUDGE: The use of CTXT_NEW is a kludge, to get the T_SIP symbol // for the SIP version request_line: T_METHOD { CTXT_URI; } T_URI { CTXT_NEW; } sip_version T_CRLF { MSG = new t_request(); MEMMAN_NEW(MSG); ((t_request *)MSG)->set_method(*$1); ((t_request *)MSG)->uri.set_url(*$3); MSG->version = *$5; MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($3); delete $3; MEMMAN_DELETE($5); delete $5; if (!((t_request *)MSG)->uri.is_valid()) { MEMMAN_DELETE(MSG); delete MSG; MSG = NULL; YYABORT; } } ; sip_version: T_SIP { CTXT_INITIAL; } '/' T_TOKEN { $$ = $4; } ; response: status_line headers T_CRLF { /* Parsing stops here. Remaining text is * not parsed. */ YYACCEPT; } ; status_line: sip_version { CTXT_NUM; } T_NUM { CTXT_LINE; } T_LINE { CTXT_INITIAL; } T_CRLF { MSG = new t_response(); MEMMAN_NEW(MSG); MSG->version = *$1; ((t_response *)MSG)->code = $3; ((t_response *)MSG)->reason = trim(*$5); MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($5); delete $5; } ; headers: /* empty */ | headers header ; header: hd_accept hdr_accept T_CRLF | hd_accept_encoding hdr_accept_encoding T_CRLF | hd_accept_language hdr_accept_language T_CRLF | hd_alert_info hdr_alert_info T_CRLF | hd_allow hdr_allow T_CRLF | hd_allow_events hdr_allow_events T_CRLF | hd_authentication_info hdr_authentication_info T_CRLF | hd_authorization hdr_authorization T_CRLF | hd_call_id hdr_call_id T_CRLF | hd_call_info hdr_call_info T_CRLF | hd_contact hdr_contact T_CRLF | hd_content_disp hdr_content_disp T_CRLF | hd_content_encoding hdr_content_encoding T_CRLF | hd_content_language hdr_content_language T_CRLF | hd_content_length hdr_content_length T_CRLF | hd_content_type hdr_content_type T_CRLF | hd_cseq hdr_cseq T_CRLF | hd_date hdr_date T_CRLF | hd_event hdr_event T_CRLF | hd_error_info hdr_error_info T_CRLF | hd_expires hdr_expires T_CRLF | hd_from hdr_from T_CRLF | hd_in_reply_to hdr_in_reply_to T_CRLF | hd_max_forwards hdr_max_forwards T_CRLF | hd_min_expires hdr_min_expires T_CRLF | hd_mime_version hdr_mime_version T_CRLF | hd_organization hdr_organization T_CRLF | hd_p_asserted_identity hdr_p_asserted_identity T_CRLF | hd_p_preferred_identity hdr_p_preferred_identity T_CRLF | hd_priority hdr_priority T_CRLF | hd_privacy hdr_privacy T_CRLF | hd_proxy_authenticate hdr_proxy_authenticate T_CRLF | hd_proxy_authorization hdr_proxy_authorization T_CRLF | hd_proxy_require hdr_proxy_require T_CRLF | hd_rack hdr_rack T_CRLF | hd_record_route hdr_record_route T_CRLF | hd_refer_sub hdr_refer_sub T_CRLF | hd_refer_to hdr_refer_to T_CRLF | hd_referred_by hdr_referred_by T_CRLF | hd_replaces hdr_replaces T_CRLF | hd_reply_to hdr_reply_to T_CRLF | hd_require hdr_require T_CRLF | hd_request_disposition hdr_request_disposition T_CRLF | hd_retry_after hdr_retry_after T_CRLF | hd_route hdr_route T_CRLF | hd_rseq hdr_rseq T_CRLF | hd_server hdr_server T_CRLF | hd_service_route hdr_service_route T_CRLF | hd_sip_etag hdr_sip_etag T_CRLF | hd_sip_if_match hdr_sip_if_match T_CRLF | hd_subject hdr_subject T_CRLF | hd_subscription_state hdr_subscription_state T_CRLF | hd_supported hdr_supported T_CRLF | hd_timestamp hdr_timestamp T_CRLF | hd_to hdr_to T_CRLF | hd_unsupported hdr_unsupported T_CRLF | hd_user_agent hdr_user_agent T_CRLF | hd_via hdr_via T_CRLF | hd_warning hdr_warning T_CRLF | hd_www_authenticate hdr_www_authenticate T_CRLF | T_HDR_UNKNOWN ':' hdr_unknown T_CRLF { MSG->add_unknown_header(*$1, trim(*$3)); MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($3); delete $3; } | hd_accept error T_CRLF { PARSE_ERROR("Accept"); } | hd_accept_encoding error T_CRLF { PARSE_ERROR("Accept-Encoding"); } | hd_accept_language error T_CRLF { PARSE_ERROR("Accept-Language"); } | hd_alert_info error T_CRLF { PARSE_ERROR("Alert-Info"); } | hd_allow error T_CRLF { PARSE_ERROR("Allow"); } | hd_allow_events error T_CRLF { PARSE_ERROR("Allow-Events"); } | hd_authentication_info error T_CRLF { PARSE_ERROR("Authentication-Info"); } | hd_authorization error T_CRLF { PARSE_ERROR("Authorization"); } | hd_call_id error T_CRLF { PARSE_ERROR("Call-ID"); } | hd_call_info error T_CRLF { PARSE_ERROR("Call-Info"); } | hd_contact error T_CRLF { PARSE_ERROR("Contact"); } | hd_content_disp error T_CRLF { PARSE_ERROR("Content-Disposition"); } | hd_content_encoding error T_CRLF { PARSE_ERROR("Content-Encoding"); } | hd_content_language error T_CRLF { PARSE_ERROR("Content-Language"); } | hd_content_length error T_CRLF { PARSE_ERROR("Content-Length"); } | hd_content_type error T_CRLF { PARSE_ERROR("Content-Type"); } | hd_cseq error T_CRLF { PARSE_ERROR("CSeq"); } | hd_date error T_CRLF { PARSE_ERROR("Date"); } | hd_error_info error T_CRLF { PARSE_ERROR("Error-Info"); } | hd_event error T_CRLF { PARSE_ERROR("Event"); } | hd_expires error T_CRLF { PARSE_ERROR("Expires"); } | hd_from error T_CRLF { PARSE_ERROR("From"); } | hd_in_reply_to error T_CRLF { PARSE_ERROR("In-Reply-To"); } | hd_max_forwards error T_CRLF { PARSE_ERROR("Max-Forwards"); } | hd_min_expires error T_CRLF { PARSE_ERROR("Min-Expires"); } | hd_mime_version error T_CRLF { PARSE_ERROR("MIME-Version"); } | hd_organization error T_CRLF { PARSE_ERROR("Organization"); } | hd_p_asserted_identity error T_CRLF { PARSE_ERROR("P-Asserted-Identity"); } | hd_p_preferred_identity error T_CRLF { PARSE_ERROR("P-Preferred-Identity"); } | hd_priority error T_CRLF { PARSE_ERROR("Priority"); } | hd_privacy error T_CRLF { PARSE_ERROR("Privacy"); } | hd_proxy_authenticate error T_CRLF { PARSE_ERROR("Proxy-Authenticate"); } | hd_proxy_authorization error T_CRLF { PARSE_ERROR("Proxy-Authorization"); } | hd_proxy_require error T_CRLF { PARSE_ERROR("Proxy-Require"); } | hd_rack error T_CRLF { PARSE_ERROR("RAck"); } | hd_record_route error T_CRLF { PARSE_ERROR("Record-Route"); } | hd_refer_sub error T_CRLF { PARSE_ERROR("Refer-Sub"); } | hd_refer_to error T_CRLF { PARSE_ERROR("Refer-To"); } | hd_referred_by error T_CRLF { PARSE_ERROR("Referred-By"); } | hd_replaces error T_CRLF { PARSE_ERROR("Replaces"); } | hd_reply_to error T_CRLF { PARSE_ERROR("Reply-To"); } | hd_require error T_CRLF { PARSE_ERROR("Require"); } | hd_request_disposition error T_CRLF { PARSE_ERROR("Request-Disposition"); } | hd_retry_after error T_CRLF { PARSE_ERROR("Retry-After"); } | hd_route error T_CRLF { PARSE_ERROR("Route"); } | hd_rseq error T_CRLF { PARSE_ERROR("RSeq"); } | hd_server error T_CRLF { PARSE_ERROR("Server"); } | hd_service_route error T_CRLF { PARSE_ERROR("Service-Route"); } | hd_sip_etag error T_CRLF { PARSE_ERROR("SIP-ETag"); } | hd_sip_if_match error T_CRLF { PARSE_ERROR("SIP-If-Match"); } | hd_subject error T_CRLF { PARSE_ERROR("Subject"); } | hd_subscription_state error T_CRLF { PARSE_ERROR("Subscription-State"); } | hd_supported error T_CRLF { PARSE_ERROR("Supported"); } | hd_timestamp error T_CRLF { PARSE_ERROR("Timestamp"); } | hd_to error T_CRLF { PARSE_ERROR("To"); } | hd_unsupported error T_CRLF { PARSE_ERROR("Unsupported"); } | hd_user_agent error T_CRLF { PARSE_ERROR("User-Agent"); } | hd_via error T_CRLF { PARSE_ERROR("Via"); } | hd_warning error T_CRLF { PARSE_ERROR("Warning"); } | hd_www_authenticate error T_CRLF { PARSE_ERROR("WWW-Authenticate"); } ; // KLUDGE // These rules are needed to get the error recovery working. // Many header rules start with a context change. As Bison uses // one token look ahead to determine a matching rule, the context // change must be done before Bison tries to match the error rule. // Changing the context header and once again at the header rule // does no harm as the context change operator is idem-potent. hd_accept: T_HDR_ACCEPT ':' ; hd_accept_encoding: T_HDR_ACCEPT_ENCODING ':' ; hd_accept_language: T_HDR_ACCEPT_LANGUAGE ':' { CTXT_LANG; } ; hd_alert_info: T_HDR_ALERT_INFO ':' ; hd_allow: T_HDR_ALLOW ':' ; hd_allow_events: T_HDR_ALLOW_EVENTS ':' ; hd_authentication_info: T_HDR_AUTHENTICATION_INFO ':' ; hd_authorization: T_HDR_AUTHORIZATION ':' { CTXT_AUTH_SCHEME; } ; hd_call_id: T_HDR_CALL_ID ':' { CTXT_WORD; } ; hd_call_info: T_HDR_CALL_INFO ':' ; hd_contact: T_HDR_CONTACT ':' { CTXT_URI_SPECIAL; } ; hd_content_disp: T_HDR_CONTENT_DISP ':' ; hd_content_encoding: T_HDR_CONTENT_ENCODING ':' ; hd_content_language: T_HDR_CONTENT_LANGUAGE ':' { CTXT_LANG; } ; hd_content_length: T_HDR_CONTENT_LENGTH ':' { CTXT_NUM; } ; hd_content_type: T_HDR_CONTENT_TYPE ':' ; hd_cseq: T_HDR_CSEQ ':' { CTXT_NUM; } ; hd_date: T_HDR_DATE ':' { CTXT_DATE;} ; hd_error_info: T_HDR_ERROR_INFO ':' ; hd_event: T_HDR_EVENT ':' ; hd_expires: T_HDR_EXPIRES ':' { CTXT_NUM; } ; hd_from: T_HDR_FROM ':' { CTXT_URI_SPECIAL; } ; hd_in_reply_to: T_HDR_IN_REPLY_TO ':' { CTXT_WORD; } ; hd_max_forwards: T_HDR_MAX_FORWARDS ':' { CTXT_NUM; } ; hd_min_expires: T_HDR_MIN_EXPIRES ':' { CTXT_NUM; } ; hd_mime_version: T_HDR_MIME_VERSION ':' ; hd_organization: T_HDR_ORGANIZATION ':' { CTXT_LINE; } ; hd_p_asserted_identity: T_HDR_P_ASSERTED_IDENTITY ':' { CTXT_URI_SPECIAL; } ; hd_p_preferred_identity: T_HDR_P_PREFERRED_IDENTITY ':' { CTXT_URI_SPECIAL; } ; hd_priority: T_HDR_PRIORITY ':' ; hd_privacy: T_HDR_PRIVACY ':' ; hd_proxy_authenticate: T_HDR_PROXY_AUTHENTICATE ':' { CTXT_AUTH_SCHEME; } ; hd_proxy_authorization: T_HDR_PROXY_AUTHORIZATION ':' { CTXT_AUTH_SCHEME; } ; hd_proxy_require: T_HDR_PROXY_REQUIRE ':' ; hd_rack: T_HDR_RACK ':' { CTXT_NUM; } ; hd_record_route: T_HDR_RECORD_ROUTE ':' { CTXT_URI; } ; hd_refer_sub: T_HDR_REFER_SUB ':' ; hd_refer_to: T_HDR_REFER_TO ':' { CTXT_URI_SPECIAL; } ; hd_referred_by: T_HDR_REFERRED_BY ':' { CTXT_URI_SPECIAL; } ; hd_replaces: T_HDR_REPLACES ':' { CTXT_WORD; } ; hd_reply_to: T_HDR_REPLY_TO ':' { CTXT_URI_SPECIAL; } ; hd_require: T_HDR_REQUIRE ':' ; hd_request_disposition: T_HDR_REQUEST_DISPOSITION ':' ; hd_retry_after: T_HDR_RETRY_AFTER ':' { CTXT_NUM; } ; hd_route: T_HDR_ROUTE ':' { CTXT_URI; } ; hd_rseq: T_HDR_RSEQ ':' { CTXT_NUM; } ; hd_server: T_HDR_SERVER ':' ; hd_service_route: T_HDR_SERVICE_ROUTE ':' { CTXT_URI; } ; hd_sip_etag: T_HDR_SIP_ETAG ':' ; hd_sip_if_match: T_HDR_SIP_IF_MATCH ':' ; hd_subject: T_HDR_SUBJECT ':' { CTXT_LINE; } ; hd_subscription_state: T_HDR_SUBSCRIPTION_STATE ':' ; hd_supported: T_HDR_SUPPORTED ':' ; hd_timestamp: T_HDR_TIMESTAMP ':' { CTXT_NUM; } ; hd_to: T_HDR_TO ':' { CTXT_URI_SPECIAL; } ; hd_unsupported: T_HDR_UNSUPPORTED ':' ; hd_user_agent: T_HDR_USER_AGENT ':' ; hd_via: T_HDR_VIA ':' ; hd_warning: T_HDR_WARNING ':' { CTXT_NUM; } ; hd_www_authenticate: T_HDR_WWW_AUTHENTICATE ':' { CTXT_AUTH_SCHEME; } ; hdr_accept: /* empty */ | media_range parameters { $1->add_params(*$2); MSG->hdr_accept.add_media(*$1); MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($2); delete $2; } | hdr_accept ',' media_range parameters { $3->add_params(*$4); MSG->hdr_accept.add_media(*$3); MEMMAN_DELETE($3); delete $3; MEMMAN_DELETE($4); delete $4; } ; media_range: T_TOKEN '/' T_TOKEN { $$ = new t_media(tolower(*$1), tolower(*$3)); MEMMAN_NEW($$); MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($3); delete $3; } ; parameters: /* empty */ { $$ = new list; MEMMAN_NEW($$); } | parameters ';' parameter { $1->push_back(*$3); $$ = $1; MEMMAN_DELETE($3); delete $3; } ; parameter: T_TOKEN { $$ = new t_parameter(tolower(*$1)); MEMMAN_NEW($$); MEMMAN_DELETE($1); delete $1; } | T_TOKEN '=' { CTXT_PARAMVAL; } parameter_val { CTXT_INITIAL; } { $$ = new t_parameter(tolower(*$1), *$4); MEMMAN_NEW($$); MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($4); delete $4; } ; parameter_val: T_PARAMVAL { $$ = $1; } | T_QSTRING { $$ = $1; } ; hdr_accept_encoding: content_coding { MSG->hdr_accept_encoding.add_coding(*$1); MEMMAN_DELETE($1); delete $1; } | hdr_accept_encoding ',' content_coding { MSG->hdr_accept_encoding.add_coding(*$3); MEMMAN_DELETE($3); delete $3; } ; content_coding: T_TOKEN { $$ = new t_coding(tolower(*$1)); MEMMAN_NEW($$); MEMMAN_DELETE($1); delete $1; } | T_TOKEN q_factor { $$ = new t_coding(tolower(*$1)); MEMMAN_NEW($$); $$->q = $2; MEMMAN_DELETE($1); delete $1; } ; q_factor: ';' parameter { if ($2->name != "q") YYERROR; $$ = atof($2->value.c_str()); MEMMAN_DELETE($2); delete $2; } ; hdr_accept_language: { CTXT_LANG; } language { MSG->hdr_accept_language.add_language(*$2); MEMMAN_DELETE($2); delete $2; } | hdr_accept_language ',' { CTXT_LANG; } language { MSG->hdr_accept_language.add_language(*$4); MEMMAN_DELETE($4); delete $4; } ; language: T_LANG { CTXT_INITIAL; $$ = new t_language(tolower(*$1)); MEMMAN_NEW($$); MEMMAN_DELETE($1); delete $1; } | T_LANG { CTXT_INITIAL; } q_factor { $$ = new t_language(tolower(*$1)); MEMMAN_NEW($$); $$->q = $3; MEMMAN_DELETE($1); delete $1; } ; hdr_alert_info: alert_param { MSG->hdr_alert_info.add_param(*$1); MEMMAN_DELETE($1); delete $1; } | hdr_alert_info ',' alert_param { MSG->hdr_alert_info.add_param(*$3); MEMMAN_DELETE($3); delete $3; } ; alert_param: '<' { CTXT_URI; } T_URI { CTXT_INITIAL; } '>' parameters { $$ = new t_alert_param(); MEMMAN_NEW($$); $$->uri.set_url(*$3); $$->parameter_list = *$6; if (!$$->uri.is_valid()) { MEMMAN_DELETE($$); delete $$; YYERROR; } MEMMAN_DELETE($3); delete $3; MEMMAN_DELETE($6); delete $6; } ; hdr_allow: T_TOKEN { MSG->hdr_allow.add_method(*$1); MEMMAN_DELETE($1); delete $1; } | hdr_allow ',' T_TOKEN { MSG->hdr_allow.add_method(*$3); MEMMAN_DELETE($3); delete $3; } ; hdr_call_id: { CTXT_WORD; } call_id { CTXT_INITIAL; } { MSG->hdr_call_id.set_call_id(*$2); MEMMAN_DELETE($2); delete $2; } ; call_id: T_WORD { $$ = $1; } | T_WORD '@' T_WORD { $$ = new string(*$1 + '@' + *$3); MEMMAN_NEW($$); MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($3); delete $3; } ; hdr_call_info: info_param { MSG->hdr_call_info.add_param(*$1); MEMMAN_DELETE($1); delete $1; } | hdr_call_info ',' info_param { MSG->hdr_call_info.add_param(*$3); MEMMAN_DELETE($3); delete $3; } ; info_param: '<' { CTXT_URI; } T_URI { CTXT_INITIAL; } '>' parameters { $$ = new t_info_param(); MEMMAN_NEW($$); $$->uri.set_url(*$3); $$->parameter_list = *$6; if (!$$->uri.is_valid()) { MEMMAN_DELETE($$); delete $$; YYERROR; } MEMMAN_DELETE($3); delete $3; MEMMAN_DELETE($6); delete $6; } ; hdr_contact: { CTXT_URI_SPECIAL; } T_URI_WILDCARD { CTXT_INITIAL; } { MSG->hdr_contact.set_any(); } | contacts { MSG->hdr_contact.add_contacts(*$1); MEMMAN_DELETE($1); delete $1; } ; contacts: contact_param { $$ = new list; MEMMAN_NEW($$); $$->push_back(*$1); MEMMAN_DELETE($1); delete $1; } | contacts ',' contact_param { $1->push_back(*$3); $$ = $1; MEMMAN_DELETE($3); delete $3; } ; contact_param: contact_addr parameters { $$ = $1; list::const_iterator i; for (i = $2->begin(); i != $2->end(); i++) { if (i->name == "q") { $$->set_qvalue(atof(i->value.c_str())); } else if (i->name == "expires") { $$->set_expires(strtoul( i->value.c_str(), NULL, 10)); } else { $$->add_extension(*i); } } MEMMAN_DELETE($2); delete $2; } ; contact_addr: { CTXT_URI_SPECIAL; } T_URI { CTXT_INITIAL; } { $$ = new t_contact_param(); MEMMAN_NEW($$); $$->uri.set_url(*$2); if (!$$->uri.is_valid()) { MEMMAN_DELETE($$); delete $$; YYERROR; } MEMMAN_DELETE($2); delete $2; } | { CTXT_URI_SPECIAL; } display_name '<' { CTXT_URI; } T_URI { CTXT_INITIAL; } '>' { $$ = new t_contact_param(); MEMMAN_NEW($$); $$->display = *$2; $$->uri.set_url(*$5); if (!$$->uri.is_valid()) { MEMMAN_DELETE($$); delete $$; YYERROR; } MEMMAN_DELETE($2); delete $2; MEMMAN_DELETE($5); delete $5; } ; display_name: /* empty */ { $$ = new string(); MEMMAN_NEW($$); } | T_DISPLAY { $$ = new string(rtrim(*$1)); MEMMAN_NEW($$); MEMMAN_DELETE($1); delete $1; } | T_QSTRING { $$ = $1; } ; hdr_content_disp: T_TOKEN parameters { MSG->hdr_content_disp.set_type(tolower(*$1)); list::const_iterator i; for (i = $2->begin(); i != $2->end(); i++) { if (i->name == "filename") { MSG->hdr_content_disp.set_filename(i->value); } else { MSG->hdr_content_disp.add_param(*i); } } MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($2); delete $2; } ; hdr_content_encoding: content_coding { MSG->hdr_content_encoding.add_coding(*$1); MEMMAN_DELETE($1); delete $1; } | hdr_content_encoding ',' content_coding { MSG->hdr_content_encoding.add_coding(*$3); MEMMAN_DELETE($3); delete $3; } ; hdr_content_language: { CTXT_LANG; } language { MSG->hdr_content_language.add_language(*$2); MEMMAN_DELETE($2); delete $2; } | hdr_content_language ',' { CTXT_LANG; } language { MSG->hdr_content_language.add_language(*$4); MEMMAN_DELETE($4); delete $4; } ; hdr_content_length: { CTXT_NUM; } T_NUM { CTXT_INITIAL; } { MSG->hdr_content_length.set_length($2); } ; hdr_content_type: media_range parameters { $1->add_params(*$2); MSG->hdr_content_type.set_media(*$1); MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($2); delete $2; } ; hdr_cseq: { CTXT_NUM; } T_NUM { CTXT_INITIAL; } T_TOKEN { MSG->hdr_cseq.set_seqnr($2); MSG->hdr_cseq.set_method(*$4); MEMMAN_DELETE($4); delete $4; } ; hdr_date: { CTXT_DATE;} T_WKDAY ',' T_NUM T_MONTH T_NUM T_NUM ':' T_NUM ':' T_NUM T_GMT { CTXT_INITIAL; } { struct tm t; t.tm_mday = $4; t.tm_mon = $5; t.tm_year = $6 - 1900; t.tm_hour = $7; t.tm_min = $9; t.tm_sec = $11; MSG->hdr_date.set_date_gm(&t); } ; hdr_error_info: error_param { MSG->hdr_error_info.add_param(*$1); MEMMAN_DELETE($1); delete $1; } | hdr_error_info ',' error_param { MSG->hdr_error_info.add_param(*$3); MEMMAN_DELETE($3); delete $3; } ; error_param: '<' { CTXT_URI; } T_URI { CTXT_INITIAL; } '>' parameters { $$ = new t_error_param(); MEMMAN_NEW($$); $$->uri.set_url(*$3); $$->parameter_list = *$6; if (!$$->uri.is_valid()) { MEMMAN_DELETE($$); delete $$; YYERROR; } MEMMAN_DELETE($3); delete $3; MEMMAN_DELETE($6); delete $6; } ; hdr_expires: { CTXT_NUM; } T_NUM { CTXT_INITIAL; } { MSG->hdr_expires.set_time($2); } ; hdr_from: { CTXT_URI_SPECIAL; } from_addr parameters { MSG->hdr_from.set_display($2->display); MSG->hdr_from.set_uri($2->uri); list::const_iterator i; for (i = $3->begin(); i != $3->end(); i++) { if (i->name == "tag") { MSG->hdr_from.set_tag(i->value); } else { MSG->hdr_from.add_param(*i); } } MEMMAN_DELETE($2); delete $2; MEMMAN_DELETE($3); delete $3; } ; from_addr: T_URI { CTXT_INITIAL; } { $$ = new t_identity(); MEMMAN_NEW($$); $$->set_uri(*$1); if (!$$->uri.is_valid()) { MEMMAN_DELETE($$); delete $$; YYERROR; } MEMMAN_DELETE($1); delete $1; } | display_name '<' { CTXT_URI; } T_URI { CTXT_INITIAL; } '>' { $$ = new t_identity(); MEMMAN_NEW($$); $$->set_display(*$1); $$->set_uri(*$4); if (!$$->uri.is_valid()) { MEMMAN_DELETE($$); delete $$; YYERROR; } MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($4); delete $4; } ; hdr_in_reply_to: { CTXT_WORD; } call_id { CTXT_INITIAL; } { MSG->hdr_in_reply_to.add_call_id(*$2); MEMMAN_DELETE($2); delete $2; } | hdr_in_reply_to ',' { CTXT_WORD; } call_id { CTXT_INITIAL; } { MSG->hdr_in_reply_to.add_call_id(*$4); MEMMAN_DELETE($4); delete $4; } ; hdr_max_forwards: { CTXT_NUM; } T_NUM { CTXT_INITIAL; } { MSG->hdr_max_forwards.set_max_forwards($2); } ; hdr_min_expires: { CTXT_NUM; } T_NUM { CTXT_INITIAL; } { MSG->hdr_min_expires.set_time($2); } ; hdr_mime_version: T_TOKEN { MSG->hdr_mime_version.set_version(*$1); MEMMAN_DELETE($1); delete $1; } ; hdr_organization: { CTXT_LINE; } T_LINE { CTXT_INITIAL; } { MSG->hdr_organization.set_name(trim(*$2)); MEMMAN_DELETE($2); delete $2; } ; hdr_p_asserted_identity: { CTXT_URI_SPECIAL; } from_addr { MSG->hdr_p_asserted_identity.add_identity(*$2); MEMMAN_DELETE($2); delete $2; } | hdr_p_asserted_identity ',' from_addr { MSG->hdr_p_asserted_identity.add_identity(*$3); MEMMAN_DELETE($3); delete $3; } ; hdr_p_preferred_identity: { CTXT_URI_SPECIAL; } from_addr { MSG->hdr_p_preferred_identity.add_identity(*$2); MEMMAN_DELETE($2); delete $2; } | hdr_p_preferred_identity ',' from_addr { MSG->hdr_p_preferred_identity.add_identity(*$3); MEMMAN_DELETE($3); delete $3; } ; hdr_priority: T_TOKEN { MSG->hdr_priority.set_priority(tolower(*$1)); MEMMAN_DELETE($1); delete $1; } ; hdr_privacy: T_TOKEN { MSG->hdr_privacy.add_privacy(tolower(*$1)); MEMMAN_DELETE($1); delete $1; } | hdr_privacy ';' T_TOKEN { MSG->hdr_privacy.add_privacy(tolower(*$3)); MEMMAN_DELETE($3); delete $3; } ; hdr_proxy_require: T_TOKEN { MSG->hdr_proxy_require.add_feature(tolower(*$1)); MEMMAN_DELETE($1); delete $1; } | hdr_proxy_require ',' T_TOKEN { MSG->hdr_proxy_require.add_feature(tolower(*$3)); MEMMAN_DELETE($3); delete $3; } ; hdr_record_route: rec_route { MSG->hdr_record_route.add_route(*$1); MEMMAN_DELETE($1); delete $1; } | hdr_record_route ',' rec_route { MSG->hdr_record_route.add_route(*$3); MEMMAN_DELETE($3); delete $3; } ; rec_route: { CTXT_URI; } display_name '<' T_URI { CTXT_INITIAL; } '>' parameters { $$ = new t_route; MEMMAN_NEW($$); $$->display = *$2; $$->uri.set_url(*$4); $$->set_params(*$7); if (!$$->uri.is_valid()) { MEMMAN_DELETE($$); delete $$; YYERROR; } MEMMAN_DELETE($2); delete $2; MEMMAN_DELETE($4); delete $4; MEMMAN_DELETE($7); delete $7; } ; hdr_service_route: rec_route { MSG->hdr_service_route.add_route(*$1); MEMMAN_DELETE($1); delete $1; } | hdr_service_route ',' rec_route { MSG->hdr_service_route.add_route(*$3); MEMMAN_DELETE($3); delete $3; } ; hdr_replaces: { CTXT_WORD; } call_id { CTXT_INITIAL; } parameters { MSG->hdr_replaces.set_call_id(*$2); list::const_iterator i; for (i = $4->begin(); i != $4->end(); i++) { if (i->name == "to-tag") { MSG->hdr_replaces.set_to_tag(i->value); } else if (i->name == "from-tag") { MSG->hdr_replaces.set_from_tag(i->value); } else if (i->name == "early-only") { MSG->hdr_replaces.set_early_only(true); } else { MSG->hdr_replaces.add_param(*i); } } if (!MSG->hdr_replaces.is_valid()) YYERROR; MEMMAN_DELETE($2); delete $2; MEMMAN_DELETE($4); delete $4; } ; hdr_reply_to: { CTXT_URI_SPECIAL; } from_addr parameters { MSG->hdr_reply_to.set_display($2->display); MSG->hdr_reply_to.set_uri($2->uri); MSG->hdr_reply_to.set_params(*$3); MEMMAN_DELETE($2); delete $2; MEMMAN_DELETE($3); delete $3; } ; hdr_require: T_TOKEN { MSG->hdr_require.add_feature(tolower(*$1)); MEMMAN_DELETE($1); delete $1; } | hdr_proxy_require ',' T_TOKEN { MSG->hdr_require.add_feature(tolower(*$3)); MEMMAN_DELETE($3); delete $3; } ; hdr_retry_after: { CTXT_NUM; } T_NUM { CTXT_INITIAL; } comment parameters { MSG->hdr_retry_after.set_time($2); MSG->hdr_retry_after.set_comment(*$4); list::const_iterator i; for (i = $5->begin(); i != $5->end(); i++) { if (i->name == "duration") { int d = strtoul(i->value.c_str(), NULL, 10); MSG->hdr_retry_after.set_duration(d); } else { MSG->hdr_retry_after.add_param(*i); } } MEMMAN_DELETE($4); delete $4; MEMMAN_DELETE($5); delete $5; } ; comment: /* empty */ { $$ = new string(); MEMMAN_NEW($$); } | '(' { CTXT_COMMENT; } T_COMMENT { CTXT_INITIAL; } ')' { $$ = $3; } ; hdr_route: rec_route { MSG->hdr_route.add_route(*$1); MEMMAN_DELETE($1); delete $1; } | hdr_route ',' rec_route { MSG->hdr_route.add_route(*$3); MEMMAN_DELETE($3); delete $3; } ; hdr_server: server { MSG->hdr_server.add_server(*$1); MEMMAN_DELETE($1); delete $1; } | hdr_server server { MSG->hdr_server.add_server(*$2); MEMMAN_DELETE($2); delete $2; } ; server: comment { $$ = new t_server(); MEMMAN_NEW($$); $$->comment = *$1; MEMMAN_DELETE($1); delete $1; } | T_TOKEN comment { $$ = new t_server(); MEMMAN_NEW($$); $$->product = *$1; $$->comment = *$2; MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($2); delete $2; } | T_TOKEN '/' T_TOKEN comment { $$ = new t_server(); MEMMAN_NEW($$); $$->product = *$1; $$->version = *$3; $$->comment = *$4; MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($3); delete $3; MEMMAN_DELETE($4); delete $4; } ; hdr_subject: { CTXT_LINE; } T_LINE { CTXT_INITIAL; } { MSG->hdr_subject.set_subject(trim(*$2)); MEMMAN_DELETE($2); delete $2; } ; hdr_supported: /* empty */ { MSG->hdr_supported.set_empty(); } | T_TOKEN { MSG->hdr_supported.add_feature(tolower(*$1)); MEMMAN_DELETE($1); delete $1; } | hdr_supported ',' T_TOKEN { MSG->hdr_supported.add_feature(tolower(*$3)); MEMMAN_DELETE($3); delete $3; } ; hdr_timestamp: { CTXT_NUM; } hdr_timestamp1 { CTXT_INITIAL; } ; hdr_timestamp1: timestamp { MSG->hdr_timestamp.set_timestamp($1); } | timestamp delay { MSG->hdr_timestamp.set_timestamp($1); MSG->hdr_timestamp.set_delay($2); } ; timestamp: T_NUM { $$ = $1; } | T_NUM '.' T_NUM { string s = int2str($1) + '.' + int2str($3); $$ = atof(s.c_str()); } ; delay: T_NUM { $$ = $1; } | T_NUM '.' T_NUM { string s = int2str($1) + '.' + int2str($3); $$ = atof(s.c_str()); } ; hdr_to: { CTXT_URI_SPECIAL; } from_addr parameters { MSG->hdr_to.set_display($2->display); MSG->hdr_to.set_uri($2->uri); list::const_iterator i; for (i = $3->begin(); i != $3->end(); i++) { if (i->name == "tag") { MSG->hdr_to.set_tag(i->value); } else { MSG->hdr_to.add_param(*i); } } MEMMAN_DELETE($2); delete $2; MEMMAN_DELETE($3); delete $3; } ; hdr_unsupported: T_TOKEN { MSG->hdr_unsupported.add_feature(tolower(*$1)); MEMMAN_DELETE($1); delete $1; } | hdr_unsupported ',' T_TOKEN { MSG->hdr_unsupported.add_feature(tolower(*$3)); MEMMAN_DELETE($3); delete $3; } ; hdr_user_agent: server { MSG->hdr_user_agent.add_server(*$1); MEMMAN_DELETE($1); delete $1; } | hdr_user_agent server { MSG->hdr_user_agent.add_server(*$2); MEMMAN_DELETE($2); delete $2; } ; hdr_via: via_parm { MSG->hdr_via.add_via(*$1); MEMMAN_DELETE($1); delete $1; } | hdr_via ',' via_parm { MSG->hdr_via.add_via(*$3); MEMMAN_DELETE($3); delete $3; } ; via_parm: sent_protocol host parameters { $$ = $1; $$->host = $2->host; $$->port = $2->port; list::const_iterator i; for (i = $3->begin(); i != $3->end(); i++) { if (i->name == "ttl") { $$->ttl = atoi(i->value.c_str()); } else if (i->name == "maddr") { $$->maddr = i->value; } else if (i->name == "received") { $$->received = i->value; } else if (i->name == "branch") { $$->branch = i->value; } else if (i->name == "rport") { $$->rport_present = true; if (i->type == t_parameter::VALUE) { $$->rport = atoi(i->value.c_str()); } } else { $$->add_extension(*i); } } MEMMAN_DELETE($2); delete $2; MEMMAN_DELETE($3); delete $3; } ; sent_protocol: T_TOKEN '/' T_TOKEN '/' T_TOKEN { $$ = new t_via(); MEMMAN_NEW($$); $$->protocol_name = toupper(*$1); $$->protocol_version = *$3; $$->transport = toupper(*$5); MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($3); delete $3; MEMMAN_DELETE($5); delete $5; } ; host: T_TOKEN { $$ = new t_via(); MEMMAN_NEW($$); $$->host = *$1; MEMMAN_DELETE($1); delete $1; } | T_TOKEN ':' { CTXT_NUM; } T_NUM { CTXT_INITIAL; } { if ($4 > 65535) YYERROR; $$ = new t_via(); MEMMAN_NEW($$); $$->host = *$1; $$->port = $4; MEMMAN_DELETE($1); delete $1; } | ipv6reference { $$ = new t_via(); MEMMAN_NEW($$); $$->host = *$1; MEMMAN_DELETE($1); delete $1; } | ipv6reference ':' { CTXT_NUM; } T_NUM { CTXT_INITIAL; } { $$ = new t_via(); MEMMAN_NEW($$); $$->host = *$1; $$->port = $4; MEMMAN_DELETE($1); delete $1; } ; ipv6reference: '[' { CTXT_IPV6ADDR; } T_IPV6ADDR { CTXT_INITIAL; } ']' { // TODO: check correct format of IPv6 address $$ = new string('[' + *$3 + ']'); MEMMAN_NEW($$); MEMMAN_DELETE($3); } ; hdr_warning: warning { MSG->hdr_warning.add_warning(*$1); MEMMAN_DELETE($1); delete $1; } | hdr_warning ',' warning { MSG->hdr_warning.add_warning(*$3); MEMMAN_DELETE($3); delete $3; } ; warning: { CTXT_NUM; } T_NUM { CTXT_INITIAL; } host T_QSTRING { $$ = new t_warning(); MEMMAN_NEW($$); $$->code = $2; $$->host = $4->host; $$->port = $4->port; $$->text = *$5; MEMMAN_DELETE($4); delete $4; MEMMAN_DELETE($5); delete $5; } ; hdr_unknown: { CTXT_LINE; } T_LINE { CTXT_INITIAL; } { $$ = $2; } ; ainfo: parameter { if ($1->name == "nextnonce") MSG->hdr_auth_info.set_next_nonce($1->value); else if ($1->name == "qop") MSG->hdr_auth_info.set_message_qop($1->value); else if ($1->name == "rspauth") MSG->hdr_auth_info.set_response_auth($1->value); else if ($1->name == "cnonce") MSG->hdr_auth_info.set_cnonce($1->value); else if ($1->name == "nc") { MSG->hdr_auth_info.set_nonce_count( hex2int($1->value)); } else { YYERROR; } MEMMAN_DELETE($1); delete $1; } ; hdr_authentication_info: ainfo | hdr_authentication_info ',' ainfo ; digest_response: parameter { $$ = new t_digest_response(); MEMMAN_NEW($$); if (!$$->set_attr(*$1)) { MEMMAN_DELETE($$); delete $$; YYERROR; } MEMMAN_DELETE($1); delete $1; } | digest_response ',' parameter { $$ = $1; if (!$$->set_attr(*$3)) { YYERROR; } MEMMAN_DELETE($3); delete $3; } ; auth_params: parameter { $$ = new list; MEMMAN_NEW($$); $$->push_back(*$1); MEMMAN_DELETE($1); delete $1; } | auth_params ',' parameter { $$ = $1; $$->push_back(*$3); MEMMAN_DELETE($3); delete $3; } ; credentials: T_AUTH_DIGEST { CTXT_INITIAL; } digest_response { $$ = new t_credentials; MEMMAN_NEW($$); $$->auth_scheme = AUTH_DIGEST; $$->digest_response = *$3; MEMMAN_DELETE($3); delete $3; } | T_AUTH_OTHER { CTXT_INITIAL; } auth_params { $$ = new t_credentials; MEMMAN_NEW($$); $$->auth_scheme = *$1; $$->auth_params = *$3; MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($3); delete $3; } ; hdr_authorization: { CTXT_AUTH_SCHEME; } credentials { MSG->hdr_authorization.add_credentials(*$2); MEMMAN_DELETE($2); delete $2; } ; digest_challenge: parameter { $$ = new t_digest_challenge(); MEMMAN_NEW($$); if (!$$->set_attr(*$1)) { MEMMAN_DELETE($$); delete $$; YYERROR; } MEMMAN_DELETE($1); delete $1; } | digest_challenge ',' parameter { $$ = $1; if (!$$->set_attr(*$3)) { YYERROR; } MEMMAN_DELETE($3); delete $3; } ; challenge: T_AUTH_DIGEST { CTXT_INITIAL; } digest_challenge { $$ = new t_challenge; MEMMAN_NEW($$); $$->auth_scheme = AUTH_DIGEST; $$->digest_challenge = *$3; MEMMAN_DELETE($3); delete $3; } | T_AUTH_OTHER { CTXT_INITIAL; } auth_params { $$ = new t_challenge; MEMMAN_NEW($$); $$->auth_scheme = *$1; $$->auth_params = *$3; MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($3); delete $3; } ; hdr_proxy_authenticate: { CTXT_AUTH_SCHEME; } challenge { MSG->hdr_proxy_authenticate.set_challenge(*$2); MEMMAN_DELETE($2); delete $2; } ; hdr_proxy_authorization: { CTXT_AUTH_SCHEME; } credentials { MSG->hdr_proxy_authorization. add_credentials(*$2); MEMMAN_DELETE($2); delete $2; } ; hdr_www_authenticate: { CTXT_AUTH_SCHEME; } challenge { MSG->hdr_www_authenticate.set_challenge(*$2); MEMMAN_DELETE($2); delete $2; } ; hdr_rseq: { CTXT_NUM; } T_NUM { CTXT_INITIAL; } { MSG->hdr_rseq.set_resp_nr($2); } ; hdr_rack: { CTXT_NUM; } T_NUM T_NUM { CTXT_INITIAL; } T_TOKEN { MSG->hdr_rack.set_resp_nr($2); MSG->hdr_rack.set_cseq_nr($3); MSG->hdr_rack.set_method(*$5); MEMMAN_DELETE($5); delete $5; } ; hdr_event: T_TOKEN parameters { MSG->hdr_event.set_event_type(tolower(*$1)); list::const_iterator i; for (i = $2->begin(); i != $2->end(); i++) { if (i->name == "id") { MSG->hdr_event.set_id(i->value); } else { MSG->hdr_event.add_event_param(*i); } } MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($2); delete $2; } ; hdr_allow_events: T_TOKEN { MSG->hdr_allow_events.add_event_type(tolower(*$1)); MEMMAN_DELETE($1); delete $1; } | hdr_allow_events ',' T_TOKEN { MSG->hdr_allow_events.add_event_type(tolower(*$3)); MEMMAN_DELETE($3); delete $3; } ; hdr_subscription_state: T_TOKEN parameters { MSG->hdr_subscription_state.set_substate(tolower(*$1)); list::const_iterator i; for (i = $2->begin(); i != $2->end(); i++) { if (i->name == "reason") { MSG->hdr_subscription_state. set_reason(tolower(i->value)); } else if (i->name == "expires") { MSG->hdr_subscription_state. set_expires(strtoul( i->value.c_str(), NULL, 10)); } else if (i->name == "retry-after") { MSG->hdr_subscription_state. set_retry_after(strtoul( i->value.c_str(), NULL, 10)); } else { MSG->hdr_subscription_state. add_extension(*i); } } MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($2); delete $2; } ; hdr_refer_to: { CTXT_URI_SPECIAL; } from_addr parameters { MSG->hdr_refer_to.set_display($2->display); MSG->hdr_refer_to.set_uri($2->uri); MSG->hdr_refer_to.set_params(*$3); MEMMAN_DELETE($2); delete $2; MEMMAN_DELETE($3); delete $3; } ; hdr_referred_by: { CTXT_URI_SPECIAL; } from_addr parameters { MSG->hdr_referred_by.set_display($2->display); MSG->hdr_referred_by.set_uri($2->uri); list::const_iterator i; for (i = $3->begin(); i != $3->end(); i++) { if (i->name == "cid") { MSG->hdr_referred_by.set_cid(i->value); } else { MSG->hdr_referred_by.add_param(*i); } } MEMMAN_DELETE($2); delete $2; MEMMAN_DELETE($3); delete $3; } ; hdr_refer_sub: T_TOKEN parameters { string value(tolower(*$1)); if (value != "true" && value != "false") { YYERROR; } MSG->hdr_refer_sub.set_create_refer_sub(value == "true"); MSG->hdr_refer_sub.set_extensions(*$2); MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($2); delete $2; } ; hdr_sip_etag: T_TOKEN { MSG->hdr_sip_etag.set_etag(*$1); MEMMAN_DELETE($1); delete $1; } ; hdr_sip_if_match: T_TOKEN { MSG->hdr_sip_if_match.set_etag(*$1); MEMMAN_DELETE($1); delete $1; } ; hdr_request_disposition: T_TOKEN { bool ret = MSG->hdr_request_disposition.set_directive(*$1); if (!ret) YYERROR; MEMMAN_DELETE($1); delete $1; } | hdr_request_disposition ',' T_TOKEN { bool ret = MSG->hdr_request_disposition.set_directive(*$3); if (!ret) YYERROR; MEMMAN_DELETE($3); delete $3; } %% void yyerror (const char *s) /* Called by yyparse on error */ { // printf ("%s\n", s); } twinkle-1.10.1/src/parser/request.cpp000066400000000000000000000470471277565361200175600ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "request.h" #include "util.h" #include "parse_ctrl.h" #include "protocol.h" #include "milenage.h" #include "audits/memman.h" #include #include using namespace UCOMMON_NAMESPACE; // AKAv1-MD5 algorithm specific helpers #define B64_ENC_SZ(x) (4 * ((x + 2) / 3)) #define B64_DEC_SZ(x) (3 * ((x + 3) / 4)) int b64_enc(const u8 * src, u8 * dst, int len) { static char tbl[64] = { 'A','B','C','D','E','F','G','H', 'I','J','K','L','M','N','O','P', 'Q','R','S','T','U','V','W','X', 'Y','Z','a','b','c','d','e','f', 'g','h','i','j','k','l','m','n', 'o','p','q','r','s','t','u','v', 'w','x','y','z','0','1','2','3', '4','5','6','7','8','9','+','/' }; u8 * dst0 = dst; int i, v, div = len / 3, mod = len % 3; for (i = 0; i < div * 3; i += 3) { v = (src[i+0] & 0xfc) >> 2; *(dst++) = tbl[v]; v = (src[i+0] & 0x03) << 4; v |= (src[i+1] & 0xf0) >> 4; *(dst++) = tbl[v]; v = (src[i+1] & 0x0f) << 2; v |= (src[i+2] & 0xc0) >> 6; *(dst++) = tbl[v]; v = src[i+2] & 0x3f; *(dst++) = tbl[v]; } if (mod == 1) { v = (src[i+0] & 0xfc) >> 2; *(dst++) = tbl[v]; v = (src[i+0] & 0x03) << 4; *(dst++) = tbl[v]; *(dst++) = '='; *(dst++) = '='; } else if (mod == 2) { v = (src[i+0] & 0xfc) >> 2; *(dst++) = tbl[v]; v = (src[i+0] & 0x03) << 4; v |= (src[i+1] & 0xf0) >> 4; *(dst++) = tbl[v]; v = (src[i+1] & 0x0f) << 2; *(dst++) = tbl[v]; *(dst++) = '='; } return dst - dst0; } static int b64_val(u8 x) { if (x >= 'A' && x <= 'Z') return x - 'A'; else if (x >= 'a' && x <= 'z') return x - 'a' + 26; else if (x >= '0' && x <= '9') return x - '0' + 52; else if (x == '+') return 62; else if (x == '/') return 63; //else if (x == '=') return -1; } int b64_dec(const u8 * src, u8 * dst, int len) { u8 * dst0 = dst; int i, x1, x2, x3, x4; if (len % 4) return 0; for (i=0; i+4 < len; i += 4) { x1 = b64_val(*(src++)); x2 = b64_val(*(src++)); x3 = b64_val(*(src++)); x4 = b64_val(*(src++)); *(dst++) = (x1 << 2) | ((x2 & 0x30) >> 4); *(dst++) = ((x2 & 0x0F) << 4) | ((x3 & 0x3C) >> 2); *(dst++) = ((x3 & 0x03) << 6) | (x4 & 0x3F); } if (len) { x1 = b64_val(*(src++)); x2 = b64_val(*(src++)); x3 = b64_val(*(src++)); x4 = b64_val(*(src++)); *(dst++) = (x1 << 2) | ((x2 & 0x30) >> 4); if (x3 != -1) { *(dst++) = ((x2 & 0x0F) << 4) | ((x3 & 0x3C) >> 2); if (x4 != -1) *(dst++) = ((x3 & 0x03) << 6) | (x4 & 0x3F); } } return dst - dst0; } #define HASH_HEX_LEN 32 #define HASH_LEN 16 // authentication with AKAv1-MD5 algorithm (RFC 3310) bool t_request::authorize_akav1_md5(const t_digest_challenge &dchlg, const std::string &username, const std::string &passwd, uint8 *op, uint8 *amf, unsigned long nc, const std::string &cnonce, const std::string &qop, std::string &resp, std::string &fail_reason) const { u8 nonce64[B64_DEC_SZ(dchlg.nonce.size())]; int len = b64_dec((const u8 *)dchlg.nonce.c_str(), nonce64, dchlg.nonce.size()); u8 rnd[AKA_RANDLEN]; u8 sqnxoraka[AKA_SQNLEN]; u8 sqn[AKA_SQNLEN]; u8 k[AKA_KLEN]; u8 res[AKA_RESLEN]; u8 ck[AKA_CKLEN]; u8 ik[AKA_IKLEN]; u8 ak[AKA_AKLEN]; int i; if (len < AKA_RANDLEN+AKA_AUTNLEN) { fail_reason = "nonce base64 data too short (need 32 bytes)"; return false; } memset(rnd, 0, AKA_RANDLEN); memset(sqnxoraka, 0, AKA_SQNLEN); memset(k, 0, AKA_KLEN); memcpy(rnd, nonce64, AKA_RANDLEN); memcpy(sqnxoraka, nonce64 + AKA_RANDLEN, AKA_SQNLEN); memcpy(k, passwd.c_str(), passwd.size()); f2345(k, rnd, res, ck, ik, ak, op); for (i=0; i < AKA_SQNLEN; i++) sqn[i] = sqnxoraka[i] ^ ak[i]; std::string res_str = std::string((char *)res, AKA_RESLEN); return authorize_md5(dchlg, username, res_str, nc, cnonce, qop, resp, fail_reason); } // authentication with MD5 algorithm bool t_request::authorize_md5(const t_digest_challenge &dchlg, const std::string &username, const std::string &passwd, unsigned long nc, const std::string &cnonce, const std::string &qop, std::string &resp, std::string &fail_reason) const { std::string A1, A2; // RFC 2617 3.2.2.2 A1 = username + ":" + dchlg.realm + ":" + passwd; // RFC 2617 3.2.2.3 if (cmp_nocase(qop, QOP_AUTH) == 0 || qop == "") { A2 = method2str(method, unknown_method) + ":" + uri.encode(); } else { A2 = method2str(method, unknown_method) + ":" + uri.encode(); A2 += ":"; if (body) { digest_t MD5body("md5"); MD5body.puts(body->encode().c_str()); A2 += std::string(MD5body.str()); } else { digest_t MD5body("md5"); MD5body.puts(""); A2 += std::string(MD5body.str()); } } // RFC 2716 3.2.2.1 // Caculate digest digest_t MD5A1("md5"); digest_t MD5A2("md5"); MD5A1.puts(A1.c_str()); MD5A2.puts(A2.c_str()); std::string x; if (cmp_nocase(qop, QOP_AUTH) == 0 || cmp_nocase(qop, QOP_AUTH_INT) == 0) { x = std::string(MD5A1.str()); x += ":"; x += dchlg.nonce + ":"; x += int2str(nc, "%08x") + ":"; x += cnonce + ":"; x += qop + ":"; x += std::string(MD5A2.str()); } else { x = std::string(MD5A1.str()); x += ":"; x += dchlg.nonce + ":"; x += std::string(MD5A2.str()); } digest_t digest("md5"); digest.puts(x.c_str()); resp = std::string(digest.str()); return true; } bool t_request::authorize(const t_challenge &chlg, t_user *user_config, const std::string &username, const std::string &passwd, unsigned long nc, const std::string &cnonce, t_credentials &cr, std::string &fail_reason) const { // Only Digest authentication is supported if (cmp_nocase(chlg.auth_scheme, AUTH_DIGEST) != 0) { fail_reason = "Authentication scheme " + chlg.auth_scheme; fail_reason += " not supported."; return false; } const t_digest_challenge &dchlg = chlg.digest_challenge; std::string qop = ""; // Determine QOP // If both auth and auth-int are supported by the server, then // choose auth to avoid problems with SIP ALGs. A SIP ALG rewrites // the body of a message, thereby breaking auth-int authentication. if (!dchlg.qop_options.empty()) { const list::const_iterator i = find( dchlg.qop_options.begin(), dchlg.qop_options.end(), QOP_AUTH_INT); const list::const_iterator j = find( dchlg.qop_options.begin(), dchlg.qop_options.end(), QOP_AUTH); if (j != dchlg.qop_options.end()) qop = QOP_AUTH; else { if (i != dchlg.qop_options.end()) qop = QOP_AUTH_INT; else { fail_reason = "Non of the qop values are supported."; return false; } } } bool ret = false; std::string resp; if (cmp_nocase(dchlg.algorithm, ALG_MD5) == 0) { ret = authorize_md5(dchlg, username, passwd, nc, cnonce, qop, resp, fail_reason); } else if (cmp_nocase(dchlg.algorithm, ALG_AKAV1_MD5) == 0) { uint8 aka_op[AKA_OPLEN]; uint8 aka_amf[AKA_AMFLEN]; user_config->get_auth_aka_op(aka_op); user_config->get_auth_aka_amf(aka_amf); ret = authorize_akav1_md5(dchlg, username, passwd, aka_op, aka_amf, nc, cnonce, qop, resp, fail_reason); } else { fail_reason = "Authentication algorithm " + dchlg.algorithm; fail_reason += " not supported."; return false; } if (!ret) return false; // Create credentials cr.auth_scheme = AUTH_DIGEST; t_digest_response &dr = cr.digest_response; dr.dresponse = resp; dr.username = username; dr.realm = dchlg.realm; dr.nonce = dchlg.nonce; dr.digest_uri = uri; dr.algorithm = dchlg.algorithm; dr.opaque = dchlg.opaque; // RFC 2617 3.2.2 if (qop != "") { dr.message_qop = qop; dr.cnonce = cnonce; dr.nonce_count = nc; } return true; } t_request::t_request() : t_sip_message(), transport_specified(false), method(METHOD_UNKNOWN) { } t_request::t_request(const t_request &r) : t_sip_message(r), destinations(r.destinations), transport_specified(r.transport_specified), uri(r.uri), method(r.method), unknown_method(r.unknown_method) { } t_request::t_request(const t_method m) : t_sip_message() { method = m; } void t_request::set_method(const std::string &s) { method = str2method(s); if (method == METHOD_UNKNOWN) { unknown_method = s; } } std::string t_request::encode(bool add_content_length) { std::string s; s = method2str(method, unknown_method) + ' ' + uri.encode(); s += " SIP/"; s += version; s += CRLF; s += t_sip_message::encode(add_content_length); return s; } list t_request::encode_env(void) { std::string s; list l = t_sip_message::encode_env(); s = "SIPREQUEST_METHOD="; s += method2str(method, unknown_method); l.push_back(s); s = "SIPREQUEST_URI="; s += uri.encode(); l.push_back(s); return l; } t_sip_message *t_request::copy(void) const { t_sip_message *m = new t_request(*this); MEMMAN_NEW(m); return m; } void t_request::set_route(const t_url &target_uri, const list &route_set) { // RFC 3261 12.2.1.1 if (route_set.empty()) { uri = target_uri; } else { if (route_set.front().uri.get_lr()) { // Loose routing uri = target_uri; for (list::const_iterator i = route_set.begin(); i != route_set.end(); ++i) { hdr_route.add_route(*i); } hdr_route.route_to_first_route = true; } else { // Strict routing uri = route_set.front().uri; for (list::const_iterator i = route_set.begin(); i != route_set.end(); ++i) { if (i != route_set.begin()) { hdr_route.add_route(*i); } } // Add target uri to the route list t_route route; route.uri = target_uri; hdr_route.add_route(route); } } } t_response *t_request::create_response(int code, std::string reason) const { t_response *r; r = new t_response(code, reason); MEMMAN_NEW(r); r->src_ip_port_request = src_ip_port; r->hdr_from = hdr_from; r->hdr_call_id = hdr_call_id; r->hdr_cseq = hdr_cseq; r->hdr_via = hdr_via; r->hdr_to = hdr_to; // Create a to-tag if none was present in the request // NOTE: 100 Trying should not get a to-tag if (hdr_to.tag.size() == 0 && code != R_100_TRYING) { r->hdr_to.set_tag(NEW_TAG); } // Server SET_HDR_SERVER(r->hdr_server); return r; } bool t_request::is_valid(bool &fatal, std::string &reason) const { if (!t_sip_message::is_valid(fatal, reason)) return false; fatal = false; if (t_parser::check_max_forwards && !hdr_max_forwards.is_populated()) { reason = "Max-Forwards header missing"; return false; } // RFC 3261 8.1.1.5 // The CSeq method must match the request method. if (hdr_cseq.method != method) { reason = "CSeq method does not match request method"; return false; } switch(method) { case INVITE: if (!hdr_contact.is_populated()) { reason = "Contact header missing"; return false; } break; case PRACK: // RFC 3262 7.1 if (!hdr_rack.is_populated()) { reason = "RAck header missing"; return false; } break; case SUBSCRIBE: // RFC 3265 7.1, 7.2 if (!hdr_contact.is_populated()) { reason = "Contact header missing"; return false; } if (!hdr_event.is_populated()) { reason = "Event header missing"; return false; } break; case NOTIFY: // RFC 3265 7.1, 7.2 if (!hdr_contact.is_populated()) { reason = "Contact header missing"; return false; } if (!hdr_event.is_populated()) { reason = "Event header missing"; return false; } // RFC 3265 7.2 // Subscription-State header is mandatory // As an exception Twinkle allows an unsollicited NOTIFY for MWI // without a Subscription-State header. Asterisk sends // unsollicited NOTIFY requests. if (!hdr_to.tag.empty() || hdr_event.event_type != SIP_EVENT_MSG_SUMMARY) { if (!hdr_subscription_state.is_populated()) { reason = "Subscription-State header missing"; return false; } } // The Subscription-State header is mandatory. // However, Asterisk uses an expired draft for sending // unsollicitied NOTIFY messages without a Subscription-State // header. As Asterisk is popular, Twinkle allows this. break; case REFER: // RFC 3515 2.4.1 if (!hdr_refer_to.is_populated()) { reason = "Refer-To header missing"; return false; } break; default: break; } if (hdr_replaces.is_populated()) { // RFC 3891 3 if (method != INVITE) { reason = "Replaces header not allowed"; return false; } } return true; } void t_request::add_destinations(const t_user &user_profile, const t_url &dst_uri) { t_url dest; if (dst_uri.get_scheme() == "tel") { // Send a tel-URI to the configure domain for the user. dest = "sip:" + user_profile.get_domain(); } else { dest = dst_uri; } if ((user_profile.get_sip_transport() == SIP_TRANS_AUTO || user_profile.get_sip_transport() == SIP_TRANS_TCP) && (dest.get_transport().empty() || cmp_nocase(dest.get_transport(), "tcp") == 0)) { list l = dest.get_h_ip_srv("tcp"); destinations.insert(destinations.end(), l.begin(), l.end()); } // Add UDP destinations after TCP, so UDP will be used as a fallback // for large messages, when TCP fails. If the message is not large, // then the TCP destinations will be removed later. // NOTE: If a message is larger than 64K, it cannot be sent via UDP if ((user_profile.get_sip_transport() == SIP_TRANS_AUTO || user_profile.get_sip_transport() == SIP_TRANS_UDP) && (dest.get_transport().empty() || cmp_nocase(dest.get_transport(), "udp") == 0) && (get_encoded_size() < 65536)) { list l = dest.get_h_ip_srv("udp"); destinations.insert(destinations.end(), l.begin(), l.end()); } transport_specified = !dest.get_transport().empty(); } void t_request::calc_destinations(const t_user &user_profile) { destinations.clear(); // Send a REGISTER to the registrar if provisioned. if (method == REGISTER && user_profile.get_use_registrar()) { add_destinations(user_profile, user_profile.get_registrar()); return; } // Bypass the proxy for an out-of-dialog SUBSCRIBE if provisioned. if (method == SUBSCRIBE && hdr_to.tag.empty()) { if (hdr_event.event_type == SIP_EVENT_MSG_SUMMARY) { if (!user_profile.get_mwi_via_proxy()) { // Take Request-URI add_destinations(user_profile, uri); return; } } } if (!user_profile.get_use_outbound_proxy() || (hdr_to.tag != "" && !user_profile.get_all_requests_to_proxy())) { // A mid dialog request will go to the host in the contact // header (put in the request-URI in this request) or route list // specified in the final response of the invite (the Route-header in // this request). // Note that an ACK for a failed INVITE (3XX-6XX) will be // sent by the transaction layer to the ipaddr/port of the // INVITE. if (hdr_route.is_populated() && hdr_route.route_to_first_route) { // Take URI from first route-header t_url &u = hdr_route.route_list.front().uri; add_destinations(user_profile, u); } else { // Take Request-URI add_destinations(user_profile, uri); } } // Send request to outbound proxy if configured if (user_profile.get_use_outbound_proxy()) { if (user_profile.get_non_resolvable_to_proxy() && !destinations.empty()) { // The destination has been resolved, so do not // use the outbound proxy in this case. return; } if (user_profile.get_all_requests_to_proxy() || hdr_to.tag == "") { // All requests should go to the proxy. // Override destination by the outbound proxy address. destinations.clear(); add_destinations(user_profile, user_profile.get_outbound_proxy()); } } } void t_request::get_destination(t_ip_port &ip_port, const t_user &user_profile) { if (destinations.empty()) calc_destinations(user_profile); // RFC 3261 18.1.1 // If the message size is larger than 1300 then the message must be // sent over TCP. // If the destination URI indicated an explicit transport, then the // destination calculation picked the possible destinations already. // The size cannot influence this calculation anymore. if (user_profile.get_sip_transport() == SIP_TRANS_AUTO && !destinations.empty() && destinations.front().transport == "tcp" && get_encoded_size() <= user_profile.get_sip_transport_udp_threshold() && !transport_specified) { // The message can be sent over UDP. Remove all TCP destinations. while (!destinations.empty() && destinations.front().transport == "tcp") { destinations.pop_front(); } } get_current_destination(ip_port); } void t_request::get_current_destination(t_ip_port &ip_port) { if (destinations.empty()) { // No destinations could be found. ip_port.transport = "udp"; ip_port.ipaddr = 0; ip_port.port = 0; } else { // Return first destination ip_port = destinations.front(); } } bool t_request::next_destination(void) { if (destinations.size() <= 1) return false; // Remove current destination destinations.pop_front(); return true; } void t_request::set_destination(const t_ip_port &ip_port) { destinations.clear(); destinations.push_back(ip_port); } bool t_request::www_authorize(const t_challenge &chlg, t_user *user_config, const std::string &username, const std::string &passwd, unsigned long nc, const std::string &cnonce, t_credentials &cr, std::string &fail_reason) { if (!authorize(chlg, user_config, username, passwd, nc, cnonce, cr, fail_reason)) { return false; } hdr_authorization.add_credentials(cr); return true; } bool t_request::proxy_authorize(const t_challenge &chlg, t_user *user_config, const std::string &username, const std::string &passwd, unsigned long nc, const std::string &cnonce, t_credentials &cr, std::string &fail_reason) { if (!authorize(chlg, user_config, username, passwd, nc, cnonce, cr, fail_reason)) { return false; } hdr_proxy_authorization.add_credentials(cr); return true; } void t_request::calc_local_ip(void) { t_ip_port dst; get_current_destination(dst); if (dst.ipaddr != 0) { local_ip_ = get_src_ip4_address_for_dst(dst.ipaddr); } } bool t_request::is_registration_request(void) const { if (method != REGISTER) return false; if (hdr_expires.is_populated() && hdr_expires.time > 0) return true; if (hdr_contact.is_populated() && !hdr_contact.contact_list.empty() && hdr_contact.contact_list.front().is_expires_present() && hdr_contact.contact_list.front().get_expires() > 0) { return true; } return false; } bool t_request::is_de_registration_request(void) const { if (method != REGISTER) return false; if (hdr_expires.is_populated() && hdr_expires.time == 0) return true; if (hdr_contact.is_populated() && !hdr_contact.contact_list.empty() && hdr_contact.contact_list.front().is_expires_present() && hdr_contact.contact_list.front().get_expires() == 0) { return true; } return false; } twinkle-1.10.1/src/parser/request.h000066400000000000000000000172511277565361200172170ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Request #ifndef _REQUEST_H #define _REQUEST_H #include #include "response.h" #include "sip_message.h" #include "sockets/url.h" #include "user.h" // Forward declaration class t_user; using namespace std; class t_request : public t_sip_message { private: /** * A DNS lookup on the request URI (or outbound proxy) might resolve * into multiple destinations. @ref get_destination() will return the first * destination. All destinations are stored here. * @ref next_destination() will remove the first destination of this * list. */ list destinations; /** * Indicates if the destination specified a transport, i.e. via the * transport parameter in a URI. */ bool transport_specified; /** * Add destinations for a given URI based on transport settings. * @param user_profile [in] User profile * @param dst_uri [in] The URI to resolve. */ void add_destinations(const t_user &user_profile, const t_url &dst_uri); /** * Calculate credentials based on the challenge. * @param chlg [in] The challenge * @param user_config [in] User configuration for user to be authorized. * @param username [in] User authentication name * @param passwd [in] Authentication password. * @param nc [in] Nonce count * @param cnonce [in] Client nonce * @param cr [out] Credentials on successful return. * @param fail_reason [out] Failure reason on failure return. * @return false, if authorization fails. * @return true, if authorization succeeded */ bool authorize(const t_challenge &chlg, t_user *user_config, const string &username, const string &passwd, unsigned long nc, const string &cnonce, t_credentials &cr, string &fail_reason) const; /** * Calculate MD5 response based on the challenge. * @param chlg [in] The challenge * @param username [in] User authentication name * @param passwd [in] Authentication password. * @param nc [in] Nonce count * @param cnonce [in] Client nonce * @param qop [in] Quality of protection * @param resp [out] Response on successful return. * @param fail_reason [out] Failure reason on failure return. * @return false, if authorization fails. * @return true, if authorization succeeded */ bool authorize_md5(const t_digest_challenge &dchlg, const string &username, const string &passwd, unsigned long nc, const string &cnonce, const string &qop, string &resp, string &fail_reason) const; /** * Calculate AKAv1-MD5 response based on the challenge. * @param chlg [in] The challenge * @param username [in] User authentication name * @param passwd [in] Authentication password. * @param op [in] Operator variant key * @param amf [in] Authentication method field * @param nc [in] Nonce count * @param cnonce [in] Client nonce * @param qop [in] Quality of protection * @param resp [out] Response on successful return. * @param fail_reason [out] Failure reason on failure return. * @return false, if authorization fails. * @return true, if authorization succeeded */ bool authorize_akav1_md5(const t_digest_challenge &dchlg, const string &username, const string &passwd, uint8 *op, uint8 *amf, unsigned long nc, const string &cnonce, const string &qop, string &resp, string &fail_reason) const; public: t_url uri; t_method method; string unknown_method; // set if method is UNKNOWN t_request(); t_request(const t_request &r); t_request(const t_method m); t_msg_type get_type(void) const { return MSG_REQUEST; } void set_method(const string &s); string encode(bool add_content_length = true); list encode_env(void); t_sip_message *copy(void) const; /** * Set the Request-URI and the Route header. * This is done according to the procedures of RFC 3261 12.2.1.1 * @param target_uri [in] The URI of the destination for this request. * @param route_set [in] The route set for this request (may be empty). */ void set_route(const t_url &target_uri, const list &route_set); // Create a response with response code based on the // request. The response is created following the general // rules in RFC 3261 8.2.6.2. // The to-hdr is simply copied from the request to the // response. // If the to-tag is missing, then // a to-tag will be generated and added to the to-header // of the response. // A specific reason may be added to the status code. t_response *create_response(int code, string reason = "") const; bool is_valid(bool &fatal, string &reason) const; // Calculate the set of possible destinations for this request. void calc_destinations(const t_user &user_profile); // Get destination to send this request to. void get_destination(t_ip_port &ip_port, const t_user &user_profile); void get_current_destination(t_ip_port &ip_port); // Move to next destination. This method should only be called after // calc_destination() was called. // Returns true if there is a next destination, otherwise returns false. bool next_destination(void); // Set a single destination to send this request to. void set_destination(const t_ip_port &ip_port); /** * Create WWW authorization credentials based on the challenge. * @param chlg [in] The challenge * @param user_config [in] User configuration for user to be authorized. * @param username [in] User authentication name * @param passwd [in] Authentication password. * @param nc [in] Nonce count * @param cnonce [in] Client nonce * @param cr [out] Credentials on successful return. * @param fail_reason [out] Failure reason on failure return. * @return false, if challenge is not supported. * @return true, if authorization succeeded */ bool www_authorize(const t_challenge &chlg, t_user *user_config, const string &username, const string &passwd, unsigned long nc, const string &cnonce, t_credentials &cr, string &fail_reason); /** * Create proxy authorization credentials based on the challenge. * @param chlg [in] The challenge * @param user_config [in] User configuration for user to be authorized. * @param username [in] User authentication name * @param passwd [in] Authentication password. * @param nc [in] Nonce count * @param cnonce [in] Client nonce * @param cr [out] Credentials on successful return. * @param fail_reason [out] Failure reason on failure return. * @return false, if challenge is not supported. * @return true, if authorization succeeded */ bool proxy_authorize(const t_challenge &chlg, t_user *user_config, const string &username, const string &passwd, unsigned long nc, const string &cnonce, t_credentials &cr, string &fail_reason); virtual void calc_local_ip(void); /** * Check if the request is a registration request. * @return True if the request is a registration request, otherwise false. */ bool is_registration_request(void) const; /** * Check if the request is a de-registration request. * @return True if the request is a de-registration request, otherwise false. */ bool is_de_registration_request(void) const; }; #endif twinkle-1.10.1/src/parser/response.cpp000066400000000000000000000142211277565361200177120ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include "response.h" #include "util.h" #include "parse_ctrl.h" #include "audits/memman.h" t_response::t_response() : t_sip_message() {} t_response::t_response(const t_response &r) : t_sip_message(r) , code(r.code), reason(r.reason), src_ip_port_request(r.src_ip_port_request) { } t_response::t_response(int _code, string _reason) : t_sip_message() { code = _code; if (_reason == "") { switch (code) { case 100: reason = REASON_100; break; case 180: reason = REASON_180; break; case 181: reason = REASON_181; break; case 182: reason = REASON_182; break; case 183: reason = REASON_183; break; case 200: reason = REASON_200; break; case 202: reason = REASON_202; break; case 300: reason = REASON_300; break; case 301: reason = REASON_301; break; case 302: reason = REASON_302; break; case 305: reason = REASON_305; break; case 380: reason = REASON_380; break; case 400: reason = REASON_400; break; case 401: reason = REASON_401; break; case 402: reason = REASON_402; break; case 403: reason = REASON_403; break; case 404: reason = REASON_404; break; case 405: reason = REASON_405; break; case 406: reason = REASON_406; break; case 407: reason = REASON_407; break; case 408: reason = REASON_408; break; case 410: reason = REASON_410; break; case 412: reason = REASON_412; break; case 413: reason = REASON_413; break; case 414: reason = REASON_414; break; case 415: reason = REASON_415; break; case 416: reason = REASON_416; break; case 420: reason = REASON_420; break; case 421: reason = REASON_421; break; case 423: reason = REASON_423; break; case 480: reason = REASON_480; break; case 481: reason = REASON_481; break; case 482: reason = REASON_482; break; case 483: reason = REASON_483; break; case 484: reason = REASON_484; break; case 485: reason = REASON_485; break; case 486: reason = REASON_486; break; case 487: reason = REASON_487; break; case 488: reason = REASON_488; break; case 489: reason = REASON_489; break; case 491: reason = REASON_491; break; case 493: reason = REASON_493; break; case 500: reason = REASON_500; break; case 501: reason = REASON_501; break; case 502: reason = REASON_502; break; case 503: reason = REASON_503; break; case 504: reason = REASON_504; break; case 505: reason = REASON_505; break; case 513: reason = REASON_513; break; case 600: reason = REASON_600; break; case 603: reason = REASON_603; break; case 604: reason = REASON_604; break; case 606: reason = REASON_606; break; default: reason = "Unknown Error"; } } else { reason = _reason; } } int t_response::get_class(void) const { return code / 100; } bool t_response::is_provisional(void) const { return (get_class() == R_1XX); } bool t_response::is_final(void) const { return (get_class() != R_1XX); } bool t_response::is_success(void) const { return (get_class() == R_2XX); } string t_response::encode(bool add_content_length) { string s; s = "SIP/" + version + ' ' + int2str(code, "%3d") + ' ' + reason; s += CRLF; s += t_sip_message::encode(add_content_length); return s; } list t_response::encode_env(void) { string s; list l = t_sip_message::encode_env(); s = "SIPSTATUS_CODE="; s += int2str(code, "%3d"); l.push_back(s); s = "SIPSTATUS_REASON="; s += reason; l.push_back(s); return l; } t_sip_message *t_response::copy(void) const { t_sip_message *m = new t_response(*this); MEMMAN_NEW(m); return m; } bool t_response::is_valid(bool &fatal, string &reason) const { if (!t_sip_message::is_valid(fatal, reason)) return false; fatal = false; switch(hdr_cseq.method) { case INVITE: if (get_class() == R_2XX && !hdr_contact.is_populated()) { reason = "Contact header missing"; return false; } break; case SUBSCRIBE: // RFC 3265 7.1, 7.2 /* Some SIP servers do not send the mandatory Expires header. For interoperability this deviation is allowed. if (get_class()== R_2XX && !hdr_expires.is_populated()) { reason = "Expires header missing"; return false; } */ switch (code) { case R_489_BAD_EVENT: if (!hdr_allow_events.is_populated()) { reason = "Allow-Events header missing"; return false; } break; } break; case NOTIFY: // RFC 3265 7.1, 7.2 switch (code) { case R_489_BAD_EVENT: if (!hdr_allow_events.is_populated()) { reason = "Allow-Events header is missing"; return false; } break; } break; default: break; } if (hdr_rseq.is_populated()) { // RFC 3262 7.1 // The value ranges from 1 to 2**32 - 1 if (hdr_rseq.resp_nr == 0) { reason = "RSeq is zero"; return false; } } return true; } bool t_response::must_authenticate(void) const { return (code == R_401_UNAUTHORIZED && hdr_www_authenticate.is_populated() || code == R_407_PROXY_AUTH_REQUIRED && hdr_proxy_authenticate.is_populated()); } void t_response::get_destination(t_ip_port &ip_port) const { assert(hdr_via.is_populated()); if (src_ip_port_request.transport == "tcp") { // RFC 3261 18.2.2 // For TCP the response should be sent on the connection on which // the request was received. So the address returned here is the // alternative destination when the connection is closed already. ip_port = src_ip_port_request; } else { hdr_via.get_response_dst(ip_port); } } void t_response::calc_local_ip(void) { t_ip_port dst; get_destination(dst); if (dst.ipaddr != 0) { local_ip_ = get_src_ip4_address_for_dst(dst.ipaddr); } } twinkle-1.10.1/src/parser/response.h000066400000000000000000000142661277565361200173700ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Response #ifndef _H_RESPONSE #define _H_RESPONSE #include #include "sip_message.h" using namespace std; // Repsonse codes // Informational #define R_100_TRYING 100 #define R_180_RINGING 180 #define R_181_CALL_IS_BEING_FORWARDED 181 #define R_182_QUEUED 182 #define R_183_SESSION_PROGRESS 183 // Success #define R_200_OK 200 #define R_202_ACCEPTED 202 // Redirection #define R_300_MULTIPLE_CHOICES 300 #define R_301_MOVED_PERMANENTLY 301 #define R_302_MOVED_TEMPORARILY 302 #define R_305_USE_PROXY 305 #define R_380_ALTERNATIVE_SERVICE 380 // Client error #define R_400_BAD_REQUEST 400 #define R_401_UNAUTHORIZED 401 #define R_402_PAYMENT_REQUIRED 402 #define R_403_FORBIDDEN 403 #define R_404_NOT_FOUND 404 #define R_405_METHOD_NOT_ALLOWED 405 #define R_406_NOT_ACCEPTABLE 406 #define R_407_PROXY_AUTH_REQUIRED 407 #define R_408_REQUEST_TIMEOUT 408 #define R_410_GONE 410 #define R_412_CONDITIONAL_REQUEST_FAILED 412 #define R_413_REQ_ENTITY_TOO_LARGE 413 #define R_414_REQ_URI_TOO_LARGE 414 #define R_415_UNSUPPORTED_MEDIA_TYPE 415 #define R_416_UNSUPPORTED_URI_SCHEME 416 #define R_420_BAD_EXTENSION 420 #define R_421_EXTENSION_REQUIRED 421 #define R_423_INTERVAL_TOO_BRIEF 423 #define R_480_TEMP_NOT_AVAILABLE 480 #define R_481_TRANSACTION_NOT_EXIST 481 #define R_482_LOOP_DETECTED 482 #define R_483_TOO_MANY_HOPS 483 #define R_484_ADDRESS_INCOMPLETE 484 #define R_485_AMBIGUOUS 485 #define R_486_BUSY_HERE 486 #define R_487_REQUEST_TERMINATED 487 #define R_488_NOT_ACCEPTABLE_HERE 488 #define R_489_BAD_EVENT 489 #define R_491_REQUEST_PENDING 491 #define R_493_UNDECIPHERABLE 493 // Server error #define R_500_INTERNAL_SERVER_ERROR 500 #define R_501_NOT_IMPLEMENTED 501 #define R_502_BAD_GATEWAY 502 #define R_503_SERVICE_UNAVAILABLE 503 #define R_504_SERVER_TIMEOUT 504 #define R_505_SIP_VERSION_NOT_SUPPORTED 505 #define R_513_MESSAGE_TOO_LARGE 513 // Global failure #define R_600_BUSY_EVERYWHERE 600 #define R_603_DECLINE 603 #define R_604_NOT_EXIST_ANYWHERE 604 #define R_606_NOT_ACCEPTABLE 606 // Response classes #define R_1XX 1 // Informational #define R_2XX 2 // Success #define R_3XX 3 // Redirection #define R_4XX 4 // Client error #define R_5XX 5 // Server error #define R_6XX 6 // Global failure // Default reason strings #define REASON_100 "Trying" #define REASON_180 "Ringing" #define REASON_181 "Call Is Being Forwarded" #define REASON_182 "Queued" #define REASON_183 "Session Progress" #define REASON_200 "OK" #define REASON_202 "Accepted" #define REASON_300 "Multiple Choices" #define REASON_301 "Moved Permanently" #define REASON_302 "Moved Temporarily" #define REASON_305 "Use Proxy" #define REASON_380 "Alternative Service" #define REASON_400 "Bad Request" #define REASON_401 "Unauthorized" #define REASON_402 "Payment Required" #define REASON_403 "Forbidden" #define REASON_404 "Not Found" #define REASON_405 "Method Not Allowed" #define REASON_406 "Not Acceptable" #define REASON_407 "Proxy Authentication Required" #define REASON_408 "Request Timeout" #define REASON_410 "Gone" #define REASON_412 "Conditional Request Failed" #define REASON_413 "Request Entity Too Large" #define REASON_414 "Request-URI Too Large" #define REASON_415 "Unsupported Media Type" #define REASON_416 "Unsupported URI Scheme" #define REASON_420 "Bad Extension" #define REASON_421 "Extension Required" #define REASON_423 "Interval Too Brief" #define REASON_480 "Temporarily Not Available" #define REASON_481 "Call Leg/Transaction Does Not Exist" #define REASON_482 "Loop Detected" #define REASON_483 "Too Many Hops" #define REASON_484 "Address Incomplete" #define REASON_485 "Ambiguous" #define REASON_486 "Busy Here" #define REASON_487 "Request Terminated" #define REASON_488 "Not Acceptable Here" #define REASON_489 "Bad Event" #define REASON_491 "Request Pending" #define REASON_493 "Undecipherable" #define REASON_500 "Internal Server Error" #define REASON_501 "Not Implemented" #define REASON_502 "Bad Gateway" #define REASON_503 "Service Unavailable" #define REASON_504 "Server Time-out" #define REASON_505 "SIP Version Not Supported" #define REASON_513 "Message Too Large" #define REASON_600 "Busy Everywhere" #define REASON_603 "Decline" #define REASON_604 "Does Not Exist Anywhere" #define REASON_606 "Not Acceptable" // The protocol allows a SIP response to have a non-default reason // phrase that gives a more detailed reason. // RFC 3261 21.4.18 // Code 480 should have a specific reason phrase #define REASON_480_NO_ANSWER "User not responding" // RFC 3265 3.2.4 #define REASON_481_SUBSCRIPTION_NOT_EXIST "Subscription does not exist" class t_response : public t_sip_message { public: int code; string reason; /** The source address of the request generating this response. */ t_ip_port src_ip_port_request; t_response(); t_response(const t_response &r); t_response(int _code, string _reason = ""); t_msg_type get_type(void) const { return MSG_RESPONSE; } // Return the response class 1,2,3,4,5,6 int get_class(void) const; bool is_provisional(void) const; bool is_final(void) const; bool is_success(void) const; string encode(bool add_content_length = true); list encode_env(void); t_sip_message *copy(void) const; bool is_valid(bool &fatal, string &reason) const; // Returns true if the response is a 401/407 with // the proper authenticate header. bool must_authenticate(void) const; /** * Get the destination address for sending the response. * @param ip_port [out] The destination address. */ void get_destination(t_ip_port &ip_port) const; virtual void calc_local_ip(void); }; #endif twinkle-1.10.1/src/parser/rijndael.cpp000066400000000000000000000660271277565361200176570ustar00rootroot00000000000000/*------------------------------------------------------------------- * Rijndael Implementation *------------------------------------------------------------------- * * A sample 32-bit orientated implementation of Rijndael, the * suggested kernel for the example 3GPP authentication and key * agreement functions. * * This implementation draws on the description in section 5.2 of * the AES proposal and also on the implementation by * Dr B. R. Gladman 9th October 2000. * It uses a number of large (4k) lookup tables to implement the * algorithm in an efficient manner. * * Note: in this implementation the State is stored in four 32-bit * words, one per column of the State, with the top byte of the * column being the _least_ significant byte of the word. * *-----------------------------------------------------------------*/ #include "twinkle_config.h" typedef unsigned char u8; typedef unsigned int u32; /* Circular byte rotates of 32 bit values */ #define rot1(x) ((x << 8) | (x >> 24)) #define rot2(x) ((x << 16) | (x >> 16)) #define rot3(x) ((x << 24) | (x >> 8)) /* Extract a byte from a 32-bit u32 */ #define byte0(x) ((u8)(x)) #define byte1(x) ((u8)(x >> 8)) #define byte2(x) ((u8)(x >> 16)) #define byte3(x) ((u8)(x >> 24)) /* Put or get a 32 bit u32 (v) in machine order from a byte * * address in (x) */ #ifndef WORDS_BIGENDIAN #define u32_in(x) (*(u32*)(x)) #define u32_out(x,y) (*(u32*)(x) = y) #else /* Invert byte order in a 32 bit variable */ __inline u32 byte_swap(const u32 x) { return rot1(x) & 0x00ff00ff | rot3(x) & 0xff00ff00; } __inline u32 u32_in(const u8 x[]) { return byte_swap(*(u32*)x); }; __inline void u32_out(u8 x[], const u32 v) { *(u32*)x = byte_swap(v); }; #endif /*--------------- The lookup tables ----------------------------*/ static u32 rnd_con[10] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36 }; static u32 ft_tab[4][256] = { { 0xA56363C6,0x847C7CF8,0x997777EE,0x8D7B7BF6,0x0DF2F2FF,0xBD6B6BD6,0xB16F6FDE,0x54C5C591, 0x50303060,0x03010102,0xA96767CE,0x7D2B2B56,0x19FEFEE7,0x62D7D7B5,0xE6ABAB4D,0x9A7676EC, 0x45CACA8F,0x9D82821F,0x40C9C989,0x877D7DFA,0x15FAFAEF,0xEB5959B2,0xC947478E,0x0BF0F0FB, 0xECADAD41,0x67D4D4B3,0xFDA2A25F,0xEAAFAF45,0xBF9C9C23,0xF7A4A453,0x967272E4,0x5BC0C09B, 0xC2B7B775,0x1CFDFDE1,0xAE93933D,0x6A26264C,0x5A36366C,0x413F3F7E,0x02F7F7F5,0x4FCCCC83, 0x5C343468,0xF4A5A551,0x34E5E5D1,0x08F1F1F9,0x937171E2,0x73D8D8AB,0x53313162,0x3F15152A, 0x0C040408,0x52C7C795,0x65232346,0x5EC3C39D,0x28181830,0xA1969637,0x0F05050A,0xB59A9A2F, 0x0907070E,0x36121224,0x9B80801B,0x3DE2E2DF,0x26EBEBCD,0x6927274E,0xCDB2B27F,0x9F7575EA, 0x1B090912,0x9E83831D,0x742C2C58,0x2E1A1A34,0x2D1B1B36,0xB26E6EDC,0xEE5A5AB4,0xFBA0A05B, 0xF65252A4,0x4D3B3B76,0x61D6D6B7,0xCEB3B37D,0x7B292952,0x3EE3E3DD,0x712F2F5E,0x97848413, 0xF55353A6,0x68D1D1B9,0000000000,0x2CEDEDC1,0x60202040,0x1FFCFCE3,0xC8B1B179,0xED5B5BB6, 0xBE6A6AD4,0x46CBCB8D,0xD9BEBE67,0x4B393972,0xDE4A4A94,0xD44C4C98,0xE85858B0,0x4ACFCF85, 0x6BD0D0BB,0x2AEFEFC5,0xE5AAAA4F,0x16FBFBED,0xC5434386,0xD74D4D9A,0x55333366,0x94858511, 0xCF45458A,0x10F9F9E9,0x06020204,0x817F7FFE,0xF05050A0,0x443C3C78,0xBA9F9F25,0xE3A8A84B, 0xF35151A2,0xFEA3A35D,0xC0404080,0x8A8F8F05,0xAD92923F,0xBC9D9D21,0x48383870,0x04F5F5F1, 0xDFBCBC63,0xC1B6B677,0x75DADAAF,0x63212142,0x30101020,0x1AFFFFE5,0x0EF3F3FD,0x6DD2D2BF, 0x4CCDCD81,0x140C0C18,0x35131326,0x2FECECC3,0xE15F5FBE,0xA2979735,0xCC444488,0x3917172E, 0x57C4C493,0xF2A7A755,0x827E7EFC,0x473D3D7A,0xAC6464C8,0xE75D5DBA,0x2B191932,0x957373E6, 0xA06060C0,0x98818119,0xD14F4F9E,0x7FDCDCA3,0x66222244,0x7E2A2A54,0xAB90903B,0x8388880B, 0xCA46468C,0x29EEEEC7,0xD3B8B86B,0x3C141428,0x79DEDEA7,0xE25E5EBC,0x1D0B0B16,0x76DBDBAD, 0x3BE0E0DB,0x56323264,0x4E3A3A74,0x1E0A0A14,0xDB494992,0x0A06060C,0x6C242448,0xE45C5CB8, 0x5DC2C29F,0x6ED3D3BD,0xEFACAC43,0xA66262C4,0xA8919139,0xA4959531,0x37E4E4D3,0x8B7979F2, 0x32E7E7D5,0x43C8C88B,0x5937376E,0xB76D6DDA,0x8C8D8D01,0x64D5D5B1,0xD24E4E9C,0xE0A9A949, 0xB46C6CD8,0xFA5656AC,0x07F4F4F3,0x25EAEACF,0xAF6565CA,0x8E7A7AF4,0xE9AEAE47,0x18080810, 0xD5BABA6F,0x887878F0,0x6F25254A,0x722E2E5C,0x241C1C38,0xF1A6A657,0xC7B4B473,0x51C6C697, 0x23E8E8CB,0x7CDDDDA1,0x9C7474E8,0x211F1F3E,0xDD4B4B96,0xDCBDBD61,0x868B8B0D,0x858A8A0F, 0x907070E0,0x423E3E7C,0xC4B5B571,0xAA6666CC,0xD8484890,0x05030306,0x01F6F6F7,0x120E0E1C, 0xA36161C2,0x5F35356A,0xF95757AE,0xD0B9B969,0x91868617,0x58C1C199,0x271D1D3A,0xB99E9E27, 0x38E1E1D9,0x13F8F8EB,0xB398982B,0x33111122,0xBB6969D2,0x70D9D9A9,0x898E8E07,0xA7949433, 0xB69B9B2D,0x221E1E3C,0x92878715,0x20E9E9C9,0x49CECE87,0xFF5555AA,0x78282850,0x7ADFDFA5, 0x8F8C8C03,0xF8A1A159,0x80898909,0x170D0D1A,0xDABFBF65,0x31E6E6D7,0xC6424284,0xB86868D0, 0xC3414182,0xB0999929,0x772D2D5A,0x110F0F1E,0xCBB0B07B,0xFC5454A8,0xD6BBBB6D,0x3A16162C }, { 0x6363C6A5,0x7C7CF884,0x7777EE99,0x7B7BF68D,0xF2F2FF0D,0x6B6BD6BD,0x6F6FDEB1,0xC5C59154, 0x30306050,0x01010203,0x6767CEA9,0x2B2B567D,0xFEFEE719,0xD7D7B562,0xABAB4DE6,0x7676EC9A, 0xCACA8F45,0x82821F9D,0xC9C98940,0x7D7DFA87,0xFAFAEF15,0x5959B2EB,0x47478EC9,0xF0F0FB0B, 0xADAD41EC,0xD4D4B367,0xA2A25FFD,0xAFAF45EA,0x9C9C23BF,0xA4A453F7,0x7272E496,0xC0C09B5B, 0xB7B775C2,0xFDFDE11C,0x93933DAE,0x26264C6A,0x36366C5A,0x3F3F7E41,0xF7F7F502,0xCCCC834F, 0x3434685C,0xA5A551F4,0xE5E5D134,0xF1F1F908,0x7171E293,0xD8D8AB73,0x31316253,0x15152A3F, 0x0404080C,0xC7C79552,0x23234665,0xC3C39D5E,0x18183028,0x969637A1,0x05050A0F,0x9A9A2FB5, 0x07070E09,0x12122436,0x80801B9B,0xE2E2DF3D,0xEBEBCD26,0x27274E69,0xB2B27FCD,0x7575EA9F, 0x0909121B,0x83831D9E,0x2C2C5874,0x1A1A342E,0x1B1B362D,0x6E6EDCB2,0x5A5AB4EE,0xA0A05BFB, 0x5252A4F6,0x3B3B764D,0xD6D6B761,0xB3B37DCE,0x2929527B,0xE3E3DD3E,0x2F2F5E71,0x84841397, 0x5353A6F5,0xD1D1B968,0000000000,0xEDEDC12C,0x20204060,0xFCFCE31F,0xB1B179C8,0x5B5BB6ED, 0x6A6AD4BE,0xCBCB8D46,0xBEBE67D9,0x3939724B,0x4A4A94DE,0x4C4C98D4,0x5858B0E8,0xCFCF854A, 0xD0D0BB6B,0xEFEFC52A,0xAAAA4FE5,0xFBFBED16,0x434386C5,0x4D4D9AD7,0x33336655,0x85851194, 0x45458ACF,0xF9F9E910,0x02020406,0x7F7FFE81,0x5050A0F0,0x3C3C7844,0x9F9F25BA,0xA8A84BE3, 0x5151A2F3,0xA3A35DFE,0x404080C0,0x8F8F058A,0x92923FAD,0x9D9D21BC,0x38387048,0xF5F5F104, 0xBCBC63DF,0xB6B677C1,0xDADAAF75,0x21214263,0x10102030,0xFFFFE51A,0xF3F3FD0E,0xD2D2BF6D, 0xCDCD814C,0x0C0C1814,0x13132635,0xECECC32F,0x5F5FBEE1,0x979735A2,0x444488CC,0x17172E39, 0xC4C49357,0xA7A755F2,0x7E7EFC82,0x3D3D7A47,0x6464C8AC,0x5D5DBAE7,0x1919322B,0x7373E695, 0x6060C0A0,0x81811998,0x4F4F9ED1,0xDCDCA37F,0x22224466,0x2A2A547E,0x90903BAB,0x88880B83, 0x46468CCA,0xEEEEC729,0xB8B86BD3,0x1414283C,0xDEDEA779,0x5E5EBCE2,0x0B0B161D,0xDBDBAD76, 0xE0E0DB3B,0x32326456,0x3A3A744E,0x0A0A141E,0x494992DB,0x06060C0A,0x2424486C,0x5C5CB8E4, 0xC2C29F5D,0xD3D3BD6E,0xACAC43EF,0x6262C4A6,0x919139A8,0x959531A4,0xE4E4D337,0x7979F28B, 0xE7E7D532,0xC8C88B43,0x37376E59,0x6D6DDAB7,0x8D8D018C,0xD5D5B164,0x4E4E9CD2,0xA9A949E0, 0x6C6CD8B4,0x5656ACFA,0xF4F4F307,0xEAEACF25,0x6565CAAF,0x7A7AF48E,0xAEAE47E9,0x08081018, 0xBABA6FD5,0x7878F088,0x25254A6F,0x2E2E5C72,0x1C1C3824,0xA6A657F1,0xB4B473C7,0xC6C69751, 0xE8E8CB23,0xDDDDA17C,0x7474E89C,0x1F1F3E21,0x4B4B96DD,0xBDBD61DC,0x8B8B0D86,0x8A8A0F85, 0x7070E090,0x3E3E7C42,0xB5B571C4,0x6666CCAA,0x484890D8,0x03030605,0xF6F6F701,0x0E0E1C12, 0x6161C2A3,0x35356A5F,0x5757AEF9,0xB9B969D0,0x86861791,0xC1C19958,0x1D1D3A27,0x9E9E27B9, 0xE1E1D938,0xF8F8EB13,0x98982BB3,0x11112233,0x6969D2BB,0xD9D9A970,0x8E8E0789,0x949433A7, 0x9B9B2DB6,0x1E1E3C22,0x87871592,0xE9E9C920,0xCECE8749,0x5555AAFF,0x28285078,0xDFDFA57A, 0x8C8C038F,0xA1A159F8,0x89890980,0x0D0D1A17,0xBFBF65DA,0xE6E6D731,0x424284C6,0x6868D0B8, 0x414182C3,0x999929B0,0x2D2D5A77,0x0F0F1E11,0xB0B07BCB,0x5454A8FC,0xBBBB6DD6,0x16162C3A }, { 0x63C6A563,0x7CF8847C,0x77EE9977,0x7BF68D7B,0xF2FF0DF2,0x6BD6BD6B,0x6FDEB16F,0xC59154C5, 0x30605030,0x01020301,0x67CEA967,0x2B567D2B,0xFEE719FE,0xD7B562D7,0xAB4DE6AB,0x76EC9A76, 0xCA8F45CA,0x821F9D82,0xC98940C9,0x7DFA877D,0xFAEF15FA,0x59B2EB59,0x478EC947,0xF0FB0BF0, 0xAD41ECAD,0xD4B367D4,0xA25FFDA2,0xAF45EAAF,0x9C23BF9C,0xA453F7A4,0x72E49672,0xC09B5BC0, 0xB775C2B7,0xFDE11CFD,0x933DAE93,0x264C6A26,0x366C5A36,0x3F7E413F,0xF7F502F7,0xCC834FCC, 0x34685C34,0xA551F4A5,0xE5D134E5,0xF1F908F1,0x71E29371,0xD8AB73D8,0x31625331,0x152A3F15, 0x04080C04,0xC79552C7,0x23466523,0xC39D5EC3,0x18302818,0x9637A196,0x050A0F05,0x9A2FB59A, 0x070E0907,0x12243612,0x801B9B80,0xE2DF3DE2,0xEBCD26EB,0x274E6927,0xB27FCDB2,0x75EA9F75, 0x09121B09,0x831D9E83,0x2C58742C,0x1A342E1A,0x1B362D1B,0x6EDCB26E,0x5AB4EE5A,0xA05BFBA0, 0x52A4F652,0x3B764D3B,0xD6B761D6,0xB37DCEB3,0x29527B29,0xE3DD3EE3,0x2F5E712F,0x84139784, 0x53A6F553,0xD1B968D1,0000000000,0xEDC12CED,0x20406020,0xFCE31FFC,0xB179C8B1,0x5BB6ED5B, 0x6AD4BE6A,0xCB8D46CB,0xBE67D9BE,0x39724B39,0x4A94DE4A,0x4C98D44C,0x58B0E858,0xCF854ACF, 0xD0BB6BD0,0xEFC52AEF,0xAA4FE5AA,0xFBED16FB,0x4386C543,0x4D9AD74D,0x33665533,0x85119485, 0x458ACF45,0xF9E910F9,0x02040602,0x7FFE817F,0x50A0F050,0x3C78443C,0x9F25BA9F,0xA84BE3A8, 0x51A2F351,0xA35DFEA3,0x4080C040,0x8F058A8F,0x923FAD92,0x9D21BC9D,0x38704838,0xF5F104F5, 0xBC63DFBC,0xB677C1B6,0xDAAF75DA,0x21426321,0x10203010,0xFFE51AFF,0xF3FD0EF3,0xD2BF6DD2, 0xCD814CCD,0x0C18140C,0x13263513,0xECC32FEC,0x5FBEE15F,0x9735A297,0x4488CC44,0x172E3917, 0xC49357C4,0xA755F2A7,0x7EFC827E,0x3D7A473D,0x64C8AC64,0x5DBAE75D,0x19322B19,0x73E69573, 0x60C0A060,0x81199881,0x4F9ED14F,0xDCA37FDC,0x22446622,0x2A547E2A,0x903BAB90,0x880B8388, 0x468CCA46,0xEEC729EE,0xB86BD3B8,0x14283C14,0xDEA779DE,0x5EBCE25E,0x0B161D0B,0xDBAD76DB, 0xE0DB3BE0,0x32645632,0x3A744E3A,0x0A141E0A,0x4992DB49,0x060C0A06,0x24486C24,0x5CB8E45C, 0xC29F5DC2,0xD3BD6ED3,0xAC43EFAC,0x62C4A662,0x9139A891,0x9531A495,0xE4D337E4,0x79F28B79, 0xE7D532E7,0xC88B43C8,0x376E5937,0x6DDAB76D,0x8D018C8D,0xD5B164D5,0x4E9CD24E,0xA949E0A9, 0x6CD8B46C,0x56ACFA56,0xF4F307F4,0xEACF25EA,0x65CAAF65,0x7AF48E7A,0xAE47E9AE,0x08101808, 0xBA6FD5BA,0x78F08878,0x254A6F25,0x2E5C722E,0x1C38241C,0xA657F1A6,0xB473C7B4,0xC69751C6, 0xE8CB23E8,0xDDA17CDD,0x74E89C74,0x1F3E211F,0x4B96DD4B,0xBD61DCBD,0x8B0D868B,0x8A0F858A, 0x70E09070,0x3E7C423E,0xB571C4B5,0x66CCAA66,0x4890D848,0x03060503,0xF6F701F6,0x0E1C120E, 0x61C2A361,0x356A5F35,0x57AEF957,0xB969D0B9,0x86179186,0xC19958C1,0x1D3A271D,0x9E27B99E, 0xE1D938E1,0xF8EB13F8,0x982BB398,0x11223311,0x69D2BB69,0xD9A970D9,0x8E07898E,0x9433A794, 0x9B2DB69B,0x1E3C221E,0x87159287,0xE9C920E9,0xCE8749CE,0x55AAFF55,0x28507828,0xDFA57ADF, 0x8C038F8C,0xA159F8A1,0x89098089,0x0D1A170D,0xBF65DABF,0xE6D731E6,0x4284C642,0x68D0B868, 0x4182C341,0x9929B099,0x2D5A772D,0x0F1E110F,0xB07BCBB0,0x54A8FC54,0xBB6DD6BB,0x162C3A16 }, { 0xC6A56363,0xF8847C7C,0xEE997777,0xF68D7B7B,0xFF0DF2F2,0xD6BD6B6B,0xDEB16F6F,0x9154C5C5, 0x60503030,0x02030101,0xCEA96767,0x567D2B2B,0xE719FEFE,0xB562D7D7,0x4DE6ABAB,0xEC9A7676, 0x8F45CACA,0x1F9D8282,0x8940C9C9,0xFA877D7D,0xEF15FAFA,0xB2EB5959,0x8EC94747,0xFB0BF0F0, 0x41ECADAD,0xB367D4D4,0x5FFDA2A2,0x45EAAFAF,0x23BF9C9C,0x53F7A4A4,0xE4967272,0x9B5BC0C0, 0x75C2B7B7,0xE11CFDFD,0x3DAE9393,0x4C6A2626,0x6C5A3636,0x7E413F3F,0xF502F7F7,0x834FCCCC, 0x685C3434,0x51F4A5A5,0xD134E5E5,0xF908F1F1,0xE2937171,0xAB73D8D8,0x62533131,0x2A3F1515, 0x080C0404,0x9552C7C7,0x46652323,0x9D5EC3C3,0x30281818,0x37A19696,0x0A0F0505,0x2FB59A9A, 0x0E090707,0x24361212,0x1B9B8080,0xDF3DE2E2,0xCD26EBEB,0x4E692727,0x7FCDB2B2,0xEA9F7575, 0x121B0909,0x1D9E8383,0x58742C2C,0x342E1A1A,0x362D1B1B,0xDCB26E6E,0xB4EE5A5A,0x5BFBA0A0, 0xA4F65252,0x764D3B3B,0xB761D6D6,0x7DCEB3B3,0x527B2929,0xDD3EE3E3,0x5E712F2F,0x13978484, 0xA6F55353,0xB968D1D1,0000000000,0xC12CEDED,0x40602020,0xE31FFCFC,0x79C8B1B1,0xB6ED5B5B, 0xD4BE6A6A,0x8D46CBCB,0x67D9BEBE,0x724B3939,0x94DE4A4A,0x98D44C4C,0xB0E85858,0x854ACFCF, 0xBB6BD0D0,0xC52AEFEF,0x4FE5AAAA,0xED16FBFB,0x86C54343,0x9AD74D4D,0x66553333,0x11948585, 0x8ACF4545,0xE910F9F9,0x04060202,0xFE817F7F,0xA0F05050,0x78443C3C,0x25BA9F9F,0x4BE3A8A8, 0xA2F35151,0x5DFEA3A3,0x80C04040,0x058A8F8F,0x3FAD9292,0x21BC9D9D,0x70483838,0xF104F5F5, 0x63DFBCBC,0x77C1B6B6,0xAF75DADA,0x42632121,0x20301010,0xE51AFFFF,0xFD0EF3F3,0xBF6DD2D2, 0x814CCDCD,0x18140C0C,0x26351313,0xC32FECEC,0xBEE15F5F,0x35A29797,0x88CC4444,0x2E391717, 0x9357C4C4,0x55F2A7A7,0xFC827E7E,0x7A473D3D,0xC8AC6464,0xBAE75D5D,0x322B1919,0xE6957373, 0xC0A06060,0x19988181,0x9ED14F4F,0xA37FDCDC,0x44662222,0x547E2A2A,0x3BAB9090,0x0B838888, 0x8CCA4646,0xC729EEEE,0x6BD3B8B8,0x283C1414,0xA779DEDE,0xBCE25E5E,0x161D0B0B,0xAD76DBDB, 0xDB3BE0E0,0x64563232,0x744E3A3A,0x141E0A0A,0x92DB4949,0x0C0A0606,0x486C2424,0xB8E45C5C, 0x9F5DC2C2,0xBD6ED3D3,0x43EFACAC,0xC4A66262,0x39A89191,0x31A49595,0xD337E4E4,0xF28B7979, 0xD532E7E7,0x8B43C8C8,0x6E593737,0xDAB76D6D,0x018C8D8D,0xB164D5D5,0x9CD24E4E,0x49E0A9A9, 0xD8B46C6C,0xACFA5656,0xF307F4F4,0xCF25EAEA,0xCAAF6565,0xF48E7A7A,0x47E9AEAE,0x10180808, 0x6FD5BABA,0xF0887878,0x4A6F2525,0x5C722E2E,0x38241C1C,0x57F1A6A6,0x73C7B4B4,0x9751C6C6, 0xCB23E8E8,0xA17CDDDD,0xE89C7474,0x3E211F1F,0x96DD4B4B,0x61DCBDBD,0x0D868B8B,0x0F858A8A, 0xE0907070,0x7C423E3E,0x71C4B5B5,0xCCAA6666,0x90D84848,0x06050303,0xF701F6F6,0x1C120E0E, 0xC2A36161,0x6A5F3535,0xAEF95757,0x69D0B9B9,0x17918686,0x9958C1C1,0x3A271D1D,0x27B99E9E, 0xD938E1E1,0xEB13F8F8,0x2BB39898,0x22331111,0xD2BB6969,0xA970D9D9,0x07898E8E,0x33A79494, 0x2DB69B9B,0x3C221E1E,0x15928787,0xC920E9E9,0x8749CECE,0xAAFF5555,0x50782828,0xA57ADFDF, 0x038F8C8C,0x59F8A1A1,0x09808989,0x1A170D0D,0x65DABFBF,0xD731E6E6,0x84C64242,0xD0B86868, 0x82C34141,0x29B09999,0x5A772D2D,0x1E110F0F,0x7BCBB0B0,0xA8FC5454,0x6DD6BBBB,0x2C3A1616 } }; static u32 fl_tab[4][256] = { { 0x00000063,0x0000007C,0x00000077,0x0000007B,0x000000F2,0x0000006B,0x0000006F,0x000000C5, 0x00000030,0x00000001,0x00000067,0x0000002B,0x000000FE,0x000000D7,0x000000AB,0x00000076, 0x000000CA,0x00000082,0x000000C9,0x0000007D,0x000000FA,0x00000059,0x00000047,0x000000F0, 0x000000AD,0x000000D4,0x000000A2,0x000000AF,0x0000009C,0x000000A4,0x00000072,0x000000C0, 0x000000B7,0x000000FD,0x00000093,0x00000026,0x00000036,0x0000003F,0x000000F7,0x000000CC, 0x00000034,0x000000A5,0x000000E5,0x000000F1,0x00000071,0x000000D8,0x00000031,0x00000015, 0x00000004,0x000000C7,0x00000023,0x000000C3,0x00000018,0x00000096,0x00000005,0x0000009A, 0x00000007,0x00000012,0x00000080,0x000000E2,0x000000EB,0x00000027,0x000000B2,0x00000075, 0x00000009,0x00000083,0x0000002C,0x0000001A,0x0000001B,0x0000006E,0x0000005A,0x000000A0, 0x00000052,0x0000003B,0x000000D6,0x000000B3,0x00000029,0x000000E3,0x0000002F,0x00000084, 0x00000053,0x000000D1,0x00000000,0x000000ED,0x00000020,0x000000FC,0x000000B1,0x0000005B, 0x0000006A,0x000000CB,0x000000BE,0x00000039,0x0000004A,0x0000004C,0x00000058,0x000000CF, 0x000000D0,0x000000EF,0x000000AA,0x000000FB,0x00000043,0x0000004D,0x00000033,0x00000085, 0x00000045,0x000000F9,0x00000002,0x0000007F,0x00000050,0x0000003C,0x0000009F,0x000000A8, 0x00000051,0x000000A3,0x00000040,0x0000008F,0x00000092,0x0000009D,0x00000038,0x000000F5, 0x000000BC,0x000000B6,0x000000DA,0x00000021,0x00000010,0x000000FF,0x000000F3,0x000000D2, 0x000000CD,0x0000000C,0x00000013,0x000000EC,0x0000005F,0x00000097,0x00000044,0x00000017, 0x000000C4,0x000000A7,0x0000007E,0x0000003D,0x00000064,0x0000005D,0x00000019,0x00000073, 0x00000060,0x00000081,0x0000004F,0x000000DC,0x00000022,0x0000002A,0x00000090,0x00000088, 0x00000046,0x000000EE,0x000000B8,0x00000014,0x000000DE,0x0000005E,0x0000000B,0x000000DB, 0x000000E0,0x00000032,0x0000003A,0x0000000A,0x00000049,0x00000006,0x00000024,0x0000005C, 0x000000C2,0x000000D3,0x000000AC,0x00000062,0x00000091,0x00000095,0x000000E4,0x00000079, 0x000000E7,0x000000C8,0x00000037,0x0000006D,0x0000008D,0x000000D5,0x0000004E,0x000000A9, 0x0000006C,0x00000056,0x000000F4,0x000000EA,0x00000065,0x0000007A,0x000000AE,0x00000008, 0x000000BA,0x00000078,0x00000025,0x0000002E,0x0000001C,0x000000A6,0x000000B4,0x000000C6, 0x000000E8,0x000000DD,0x00000074,0x0000001F,0x0000004B,0x000000BD,0x0000008B,0x0000008A, 0x00000070,0x0000003E,0x000000B5,0x00000066,0x00000048,0x00000003,0x000000F6,0x0000000E, 0x00000061,0x00000035,0x00000057,0x000000B9,0x00000086,0x000000C1,0x0000001D,0x0000009E, 0x000000E1,0x000000F8,0x00000098,0x00000011,0x00000069,0x000000D9,0x0000008E,0x00000094, 0x0000009B,0x0000001E,0x00000087,0x000000E9,0x000000CE,0x00000055,0x00000028,0x000000DF, 0x0000008C,0x000000A1,0x00000089,0x0000000D,0x000000BF,0x000000E6,0x00000042,0x00000068, 0x00000041,0x00000099,0x0000002D,0x0000000F,0x000000B0,0x00000054,0x000000BB,0x00000016 }, { 0x00006300,0x00007C00,0x00007700,0x00007B00,0x0000F200,0x00006B00,0x00006F00,0x0000C500, 0x00003000,0x00000100,0x00006700,0x00002B00,0x0000FE00,0x0000D700,0x0000AB00,0x00007600, 0x0000CA00,0x00008200,0x0000C900,0x00007D00,0x0000FA00,0x00005900,0x00004700,0x0000F000, 0x0000AD00,0x0000D400,0x0000A200,0x0000AF00,0x00009C00,0x0000A400,0x00007200,0x0000C000, 0x0000B700,0x0000FD00,0x00009300,0x00002600,0x00003600,0x00003F00,0x0000F700,0x0000CC00, 0x00003400,0x0000A500,0x0000E500,0x0000F100,0x00007100,0x0000D800,0x00003100,0x00001500, 0x00000400,0x0000C700,0x00002300,0x0000C300,0x00001800,0x00009600,0x00000500,0x00009A00, 0x00000700,0x00001200,0x00008000,0x0000E200,0x0000EB00,0x00002700,0x0000B200,0x00007500, 0x00000900,0x00008300,0x00002C00,0x00001A00,0x00001B00,0x00006E00,0x00005A00,0x0000A000, 0x00005200,0x00003B00,0x0000D600,0x0000B300,0x00002900,0x0000E300,0x00002F00,0x00008400, 0x00005300,0x0000D100,0000000000,0x0000ED00,0x00002000,0x0000FC00,0x0000B100,0x00005B00, 0x00006A00,0x0000CB00,0x0000BE00,0x00003900,0x00004A00,0x00004C00,0x00005800,0x0000CF00, 0x0000D000,0x0000EF00,0x0000AA00,0x0000FB00,0x00004300,0x00004D00,0x00003300,0x00008500, 0x00004500,0x0000F900,0x00000200,0x00007F00,0x00005000,0x00003C00,0x00009F00,0x0000A800, 0x00005100,0x0000A300,0x00004000,0x00008F00,0x00009200,0x00009D00,0x00003800,0x0000F500, 0x0000BC00,0x0000B600,0x0000DA00,0x00002100,0x00001000,0x0000FF00,0x0000F300,0x0000D200, 0x0000CD00,0x00000C00,0x00001300,0x0000EC00,0x00005F00,0x00009700,0x00004400,0x00001700, 0x0000C400,0x0000A700,0x00007E00,0x00003D00,0x00006400,0x00005D00,0x00001900,0x00007300, 0x00006000,0x00008100,0x00004F00,0x0000DC00,0x00002200,0x00002A00,0x00009000,0x00008800, 0x00004600,0x0000EE00,0x0000B800,0x00001400,0x0000DE00,0x00005E00,0x00000B00,0x0000DB00, 0x0000E000,0x00003200,0x00003A00,0x00000A00,0x00004900,0x00000600,0x00002400,0x00005C00, 0x0000C200,0x0000D300,0x0000AC00,0x00006200,0x00009100,0x00009500,0x0000E400,0x00007900, 0x0000E700,0x0000C800,0x00003700,0x00006D00,0x00008D00,0x0000D500,0x00004E00,0x0000A900, 0x00006C00,0x00005600,0x0000F400,0x0000EA00,0x00006500,0x00007A00,0x0000AE00,0x00000800, 0x0000BA00,0x00007800,0x00002500,0x00002E00,0x00001C00,0x0000A600,0x0000B400,0x0000C600, 0x0000E800,0x0000DD00,0x00007400,0x00001F00,0x00004B00,0x0000BD00,0x00008B00,0x00008A00, 0x00007000,0x00003E00,0x0000B500,0x00006600,0x00004800,0x00000300,0x0000F600,0x00000E00, 0x00006100,0x00003500,0x00005700,0x0000B900,0x00008600,0x0000C100,0x00001D00,0x00009E00, 0x0000E100,0x0000F800,0x00009800,0x00001100,0x00006900,0x0000D900,0x00008E00,0x00009400, 0x00009B00,0x00001E00,0x00008700,0x0000E900,0x0000CE00,0x00005500,0x00002800,0x0000DF00, 0x00008C00,0x0000A100,0x00008900,0x00000D00,0x0000BF00,0x0000E600,0x00004200,0x00006800, 0x00004100,0x00009900,0x00002D00,0x00000F00,0x0000B000,0x00005400,0x0000BB00,0x00001600 }, { 0x00630000,0x007C0000,0x00770000,0x007B0000,0x00F20000,0x006B0000,0x006F0000,0x00C50000, 0x00300000,0x00010000,0x00670000,0x002B0000,0x00FE0000,0x00D70000,0x00AB0000,0x00760000, 0x00CA0000,0x00820000,0x00C90000,0x007D0000,0x00FA0000,0x00590000,0x00470000,0x00F00000, 0x00AD0000,0x00D40000,0x00A20000,0x00AF0000,0x009C0000,0x00A40000,0x00720000,0x00C00000, 0x00B70000,0x00FD0000,0x00930000,0x00260000,0x00360000,0x003F0000,0x00F70000,0x00CC0000, 0x00340000,0x00A50000,0x00E50000,0x00F10000,0x00710000,0x00D80000,0x00310000,0x00150000, 0x00040000,0x00C70000,0x00230000,0x00C30000,0x00180000,0x00960000,0x00050000,0x009A0000, 0x00070000,0x00120000,0x00800000,0x00E20000,0x00EB0000,0x00270000,0x00B20000,0x00750000, 0x00090000,0x00830000,0x002C0000,0x001A0000,0x001B0000,0x006E0000,0x005A0000,0x00A00000, 0x00520000,0x003B0000,0x00D60000,0x00B30000,0x00290000,0x00E30000,0x002F0000,0x00840000, 0x00530000,0x00D10000,0000000000,0x00ED0000,0x00200000,0x00FC0000,0x00B10000,0x005B0000, 0x006A0000,0x00CB0000,0x00BE0000,0x00390000,0x004A0000,0x004C0000,0x00580000,0x00CF0000, 0x00D00000,0x00EF0000,0x00AA0000,0x00FB0000,0x00430000,0x004D0000,0x00330000,0x00850000, 0x00450000,0x00F90000,0x00020000,0x007F0000,0x00500000,0x003C0000,0x009F0000,0x00A80000, 0x00510000,0x00A30000,0x00400000,0x008F0000,0x00920000,0x009D0000,0x00380000,0x00F50000, 0x00BC0000,0x00B60000,0x00DA0000,0x00210000,0x00100000,0x00FF0000,0x00F30000,0x00D20000, 0x00CD0000,0x000C0000,0x00130000,0x00EC0000,0x005F0000,0x00970000,0x00440000,0x00170000, 0x00C40000,0x00A70000,0x007E0000,0x003D0000,0x00640000,0x005D0000,0x00190000,0x00730000, 0x00600000,0x00810000,0x004F0000,0x00DC0000,0x00220000,0x002A0000,0x00900000,0x00880000, 0x00460000,0x00EE0000,0x00B80000,0x00140000,0x00DE0000,0x005E0000,0x000B0000,0x00DB0000, 0x00E00000,0x00320000,0x003A0000,0x000A0000,0x00490000,0x00060000,0x00240000,0x005C0000, 0x00C20000,0x00D30000,0x00AC0000,0x00620000,0x00910000,0x00950000,0x00E40000,0x00790000, 0x00E70000,0x00C80000,0x00370000,0x006D0000,0x008D0000,0x00D50000,0x004E0000,0x00A90000, 0x006C0000,0x00560000,0x00F40000,0x00EA0000,0x00650000,0x007A0000,0x00AE0000,0x00080000, 0x00BA0000,0x00780000,0x00250000,0x002E0000,0x001C0000,0x00A60000,0x00B40000,0x00C60000, 0x00E80000,0x00DD0000,0x00740000,0x001F0000,0x004B0000,0x00BD0000,0x008B0000,0x008A0000, 0x00700000,0x003E0000,0x00B50000,0x00660000,0x00480000,0x00030000,0x00F60000,0x000E0000, 0x00610000,0x00350000,0x00570000,0x00B90000,0x00860000,0x00C10000,0x001D0000,0x009E0000, 0x00E10000,0x00F80000,0x00980000,0x00110000,0x00690000,0x00D90000,0x008E0000,0x00940000, 0x009B0000,0x001E0000,0x00870000,0x00E90000,0x00CE0000,0x00550000,0x00280000,0x00DF0000, 0x008C0000,0x00A10000,0x00890000,0x000D0000,0x00BF0000,0x00E60000,0x00420000,0x00680000, 0x00410000,0x00990000,0x002D0000,0x000F0000,0x00B00000,0x00540000,0x00BB0000,0x00160000 }, { 0x63000000,0x7C000000,0x77000000,0x7B000000,0xF2000000,0x6B000000,0x6F000000,0xC5000000, 0x30000000,0x01000000,0x67000000,0x2B000000,0xFE000000,0xD7000000,0xAB000000,0x76000000, 0xCA000000,0x82000000,0xC9000000,0x7D000000,0xFA000000,0x59000000,0x47000000,0xF0000000, 0xAD000000,0xD4000000,0xA2000000,0xAF000000,0x9C000000,0xA4000000,0x72000000,0xC0000000, 0xB7000000,0xFD000000,0x93000000,0x26000000,0x36000000,0x3F000000,0xF7000000,0xCC000000, 0x34000000,0xA5000000,0xE5000000,0xF1000000,0x71000000,0xD8000000,0x31000000,0x15000000, 0x04000000,0xC7000000,0x23000000,0xC3000000,0x18000000,0x96000000,0x05000000,0x9A000000, 0x07000000,0x12000000,0x80000000,0xE2000000,0xEB000000,0x27000000,0xB2000000,0x75000000, 0x09000000,0x83000000,0x2C000000,0x1A000000,0x1B000000,0x6E000000,0x5A000000,0xA0000000, 0x52000000,0x3B000000,0xD6000000,0xB3000000,0x29000000,0xE3000000,0x2F000000,0x84000000, 0x53000000,0xD1000000,0000000000,0xED000000,0x20000000,0xFC000000,0xB1000000,0x5B000000, 0x6A000000,0xCB000000,0xBE000000,0x39000000,0x4A000000,0x4C000000,0x58000000,0xCF000000, 0xD0000000,0xEF000000,0xAA000000,0xFB000000,0x43000000,0x4D000000,0x33000000,0x85000000, 0x45000000,0xF9000000,0x02000000,0x7F000000,0x50000000,0x3C000000,0x9F000000,0xA8000000, 0x51000000,0xA3000000,0x40000000,0x8F000000,0x92000000,0x9D000000,0x38000000,0xF5000000, 0xBC000000,0xB6000000,0xDA000000,0x21000000,0x10000000,0xFF000000,0xF3000000,0xD2000000, 0xCD000000,0x0C000000,0x13000000,0xEC000000,0x5F000000,0x97000000,0x44000000,0x17000000, 0xC4000000,0xA7000000,0x7E000000,0x3D000000,0x64000000,0x5D000000,0x19000000,0x73000000, 0x60000000,0x81000000,0x4F000000,0xDC000000,0x22000000,0x2A000000,0x90000000,0x88000000, 0x46000000,0xEE000000,0xB8000000,0x14000000,0xDE000000,0x5E000000,0x0B000000,0xDB000000, 0xE0000000,0x32000000,0x3A000000,0x0A000000,0x49000000,0x06000000,0x24000000,0x5C000000, 0xC2000000,0xD3000000,0xAC000000,0x62000000,0x91000000,0x95000000,0xE4000000,0x79000000, 0xE7000000,0xC8000000,0x37000000,0x6D000000,0x8D000000,0xD5000000,0x4E000000,0xA9000000, 0x6C000000,0x56000000,0xF4000000,0xEA000000,0x65000000,0x7A000000,0xAE000000,0x08000000, 0xBA000000,0x78000000,0x25000000,0x2E000000,0x1C000000,0xA6000000,0xB4000000,0xC6000000, 0xE8000000,0xDD000000,0x74000000,0x1F000000,0x4B000000,0xBD000000,0x8B000000,0x8A000000, 0x70000000,0x3E000000,0xB5000000,0x66000000,0x48000000,0x03000000,0xF6000000,0x0E000000, 0x61000000,0x35000000,0x57000000,0xB9000000,0x86000000,0xC1000000,0x1D000000,0x9E000000, 0xE1000000,0xF8000000,0x98000000,0x11000000,0x69000000,0xD9000000,0x8E000000,0x94000000, 0x9B000000,0x1E000000,0x87000000,0xE9000000,0xCE000000,0x55000000,0x28000000,0xDF000000, 0x8C000000,0xA1000000,0x89000000,0x0D000000,0xBF000000,0xE6000000,0x42000000,0x68000000, 0x41000000,0x99000000,0x2D000000,0x0F000000,0xB0000000,0x54000000,0xBB000000,0x16000000 } }; /*----------------- The workspace ------------------------------*/ static u32 Ekey[44]; /* The expanded key */ /*------ The round Function. 4 table lookups and 4 Exors ------*/ #define f_rnd(x, n) \ ( ft_tab[0][byte0(x[n])] \ ^ ft_tab[1][byte1(x[(n + 1) & 3])] \ ^ ft_tab[2][byte2(x[(n + 2) & 3])] \ ^ ft_tab[3][byte3(x[(n + 3) & 3])] ) #define f_round(bo, bi, k) \ bo[0] = f_rnd(bi, 0) ^ k[0]; \ bo[1] = f_rnd(bi, 1) ^ k[1]; \ bo[2] = f_rnd(bi, 2) ^ k[2]; \ bo[3] = f_rnd(bi, 3) ^ k[3]; \ k += 4 /*--- The S Box lookup used in constructing the Key schedule ---*/ #define ls_box(x) \ ( fl_tab[0][byte0(x)] \ ^ fl_tab[1][byte1(x)] \ ^ fl_tab[2][byte2(x)] \ ^ fl_tab[3][byte3(x)] ) /*------------ The last round function (no MixColumn) ----------*/ #define lf_rnd(x, n) \ ( fl_tab[0][byte0(x[n])] \ ^ fl_tab[1][byte1(x[(n + 1) & 3])] \ ^ fl_tab[2][byte2(x[(n + 2) & 3])] \ ^ fl_tab[3][byte3(x[(n + 3) & 3])] ) /*----------------------------------------------------------- * RijndaelKeySchedule * Initialise the key schedule from a supplied key */ void RijndaelKeySchedule(u8 key[16]) { u32 t; u32 *ek=Ekey, /* pointer to the expanded key */ *rc=rnd_con; /* pointer to the round constant */ Ekey[0] = u32_in(key ); Ekey[1] = u32_in(key + 4); Ekey[2] = u32_in(key + 8); Ekey[3] = u32_in(key + 12); while(ek < Ekey + 40) { t = rot3(ek[3]); ek[4] = ek[0] ^ ls_box(t) ^ *rc++; ek[5] = ek[1] ^ ek[4]; ek[6] = ek[2] ^ ek[5]; ek[7] = ek[3] ^ ek[6]; ek += 4; } } /*----------------------------------------------------------- * RijndaelEncrypt * Encrypt an input block */ void RijndaelEncrypt(u8 in[16], u8 out[16]) { u32 b0[4], b1[4], *kp = Ekey; b0[0] = u32_in(in ) ^ *kp++; b0[1] = u32_in(in + 4) ^ *kp++; b0[2] = u32_in(in + 8) ^ *kp++; b0[3] = u32_in(in + 12) ^ *kp++; f_round(b1, b0, kp); f_round(b0, b1, kp); f_round(b1, b0, kp); f_round(b0, b1, kp); f_round(b1, b0, kp); f_round(b0, b1, kp); f_round(b1, b0, kp); f_round(b0, b1, kp); f_round(b1, b0, kp); u32_out(out, lf_rnd(b1, 0) ^ kp[0]); u32_out(out + 4, lf_rnd(b1, 1) ^ kp[1]); u32_out(out + 8, lf_rnd(b1, 2) ^ kp[2]); u32_out(out + 12, lf_rnd(b1, 3) ^ kp[3]); } twinkle-1.10.1/src/parser/rijndael.h000066400000000000000000000016021277565361200173100ustar00rootroot00000000000000/*------------------------------------------------------------------- * Example algorithms f1, f1*, f2, f3, f4, f5, f5* *------------------------------------------------------------------- * * A sample implementation of the example 3GPP authentication and * key agreement functions f1, f1*, f2, f3, f4, f5 and f5*. This is * a byte-oriented implementation of the functions, and of the block * cipher kernel function Rijndael. * * This has been coded for clarity, not necessarily for efficiency. * * The functions f2, f3, f4 and f5 share the same inputs and have * been coded together as a single function. f1, f1* and f5* are * all coded separately. * *-----------------------------------------------------------------*/ #ifndef RIJNDAEL_H #define RIJNDAEL_H void RijndaelKeySchedule( u8 key[16] ); void RijndaelEncrypt( u8 input[16], u8 output[16] ); #endif twinkle-1.10.1/src/parser/route.cpp000066400000000000000000000022511277565361200172120ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "definitions.h" #include "route.h" #include "parse_ctrl.h" #include "util.h" void t_route::add_param(const t_parameter &p) { params.push_back(p); } void t_route::set_params(const list &l) { params = l; } string t_route::encode(void) const { string s; if (display.size() > 0) { s += '"'; s += escape(display, '"'); s += '"'; s += ' '; } s += '<'; s += uri.encode(); s += '>'; s += param_list2str(params); return s; } twinkle-1.10.1/src/parser/route.h000066400000000000000000000020561277565361200166620ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Route item #ifndef _H_ROUTE #define _H_ROUTE #include #include #include "parameter.h" using namespace std; class t_route { public: string display; t_url uri; list params; void add_param(const t_parameter &p); void set_params(const list &l); string encode(void) const; }; #endif twinkle-1.10.1/src/parser/scanner.lxx000066400000000000000000000267441277565361200175530ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ %{ #include #include #include #include #include "parse_ctrl.h" #include "parser.hxx" #include "util.h" #include "audits/memman.h" using namespace std; %} %option noyywrap %option stack DIGIT [0-9] HEXDIG [0-9a-fA-F] ALPHA [a-zA-Z] CAPITALS [A-Z] ALNUM [a-zA-Z0-9] TOKEN_SYM [[:alnum:]\-\.!%\*_\+\`\'~] WORD_SYM [[:alnum:]\-\.!%\*_\+\`\'~\(\)<>:\\\"\/\[\]\?\{\}] %x C_URI %x C_URI_SPECIAL %x C_QSTRING %x C_LANG %x C_WORD %x C_NUM %x C_DATE %x C_LINE %x C_COMMENT %x C_NEW %x C_AUTH_SCHEME %x C_RPAREN %x C_IPV6ADDR %x C_PARAMVAL %% switch (t_parser::context) { case t_parser::X_URI: BEGIN(C_URI); break; case t_parser::X_URI_SPECIAL: BEGIN(C_URI_SPECIAL); break; case t_parser::X_LANG: BEGIN(C_LANG); break; case t_parser::X_WORD: BEGIN(C_WORD); break; case t_parser::X_NUM: BEGIN(C_NUM); break; case t_parser::X_DATE: BEGIN(C_DATE); break; case t_parser::X_LINE: BEGIN(C_LINE); break; case t_parser::X_COMMENT: BEGIN(C_COMMENT); break; case t_parser::X_NEW: BEGIN(C_NEW); break; case t_parser::X_AUTH_SCHEME: BEGIN(C_AUTH_SCHEME); break; case t_parser::X_IPV6ADDR: BEGIN(C_IPV6ADDR); break; case t_parser::X_PARAMVAL: BEGIN(C_PARAMVAL); break; default: BEGIN(INITIAL); } /* Headers */ ^Accept { return T_HDR_ACCEPT; } ^Accept-Encoding { return T_HDR_ACCEPT_ENCODING; } ^Accept-Language { return T_HDR_ACCEPT_LANGUAGE; } ^Alert-Info { return T_HDR_ALERT_INFO; } ^Allow { return T_HDR_ALLOW; } ^(Allow-Events)|u { return T_HDR_ALLOW_EVENTS; } ^Authentication-Info { return T_HDR_AUTHENTICATION_INFO; } ^Authorization { return T_HDR_AUTHORIZATION; } ^(Call-ID)|i { return T_HDR_CALL_ID; } ^Call-Info { return T_HDR_CALL_INFO; } ^(Contact)|m { return T_HDR_CONTACT; } ^Content-Disposition { return T_HDR_CONTENT_DISP; } ^(Content-Encoding)|e { return T_HDR_CONTENT_ENCODING; } ^Content-Language { return T_HDR_CONTENT_LANGUAGE; } ^(Content-Length)|l { return T_HDR_CONTENT_LENGTH; } ^(Content-Type)|c { return T_HDR_CONTENT_TYPE; } ^CSeq { return T_HDR_CSEQ; } ^Date { return T_HDR_DATE; } ^Error-Info { return T_HDR_ERROR_INFO; } ^(Event)|o { return T_HDR_EVENT; } ^Expires { return T_HDR_EXPIRES; } ^(From|f) { return T_HDR_FROM; } ^In-Reply-To { return T_HDR_IN_REPLY_TO; } ^Max-Forwards { return T_HDR_MAX_FORWARDS; } ^Min-Expires { return T_HDR_MIN_EXPIRES; } ^MIME-Version { return T_HDR_MIME_VERSION; } ^Organization { return T_HDR_ORGANIZATION; } ^P-Asserted-Identity { return T_HDR_P_ASSERTED_IDENTITY; } ^P-Preferred-Identity { return T_HDR_P_PREFERRED_IDENTITY; } ^Priority { return T_HDR_PRIORITY; } ^Privacy { return T_HDR_PRIVACY; } ^Proxy-Authenticate { return T_HDR_PROXY_AUTHENTICATE; } ^Proxy-Authorization { return T_HDR_PROXY_AUTHORIZATION; } ^Proxy-Require { return T_HDR_PROXY_REQUIRE; } ^RAck { return T_HDR_RACK; } ^Record-Route { return T_HDR_RECORD_ROUTE; } ^Service-Route { return T_HDR_SERVICE_ROUTE; } ^Refer-Sub { return T_HDR_REFER_SUB; } ^(Refer-To)|r { return T_HDR_REFER_TO; } ^(Referred-By)|b { return T_HDR_REFERRED_BY; } ^Replaces { return T_HDR_REPLACES; } ^Reply-To { return T_HDR_REPLY_TO; } ^Require { return T_HDR_REQUIRE; } ^(Request-Disposition)|d {return T_HDR_REQUEST_DISPOSITION; } ^Retry-After { return T_HDR_RETRY_AFTER; } ^Route { return T_HDR_ROUTE; } ^RSeq { return T_HDR_RSEQ; } ^Server { return T_HDR_SERVER; } ^SIP-ETag { return T_HDR_SIP_ETAG; } ^SIP-If-Match { return T_HDR_SIP_IF_MATCH; } ^(Subject)|s { return T_HDR_SUBJECT; } ^Subscription-State { return T_HDR_SUBSCRIPTION_STATE; } ^(Supported)|k { return T_HDR_SUPPORTED; } ^Timestamp { return T_HDR_TIMESTAMP; } ^(To)|t { return T_HDR_TO; } ^unsupported { return T_HDR_UNSUPPORTED; } ^User-Agent { return T_HDR_USER_AGENT; } ^(Via)|v { return T_HDR_VIA; } ^Warning { return T_HDR_WARNING; } ^WWW-Authenticate { return T_HDR_WWW_AUTHENTICATE; } ^{TOKEN_SYM}+ { yylval.yyt_str = new string(yytext); MEMMAN_NEW(yylval.yyt_str); return T_HDR_UNKNOWN; } /* Token as define in RFC 3261 */ {TOKEN_SYM}+ { yylval.yyt_str = new string(yytext); MEMMAN_NEW(yylval.yyt_str); return T_TOKEN; } /* Switch to quoted string context */ \" { yy_push_state(C_QSTRING); } /* End of line */ \r\n { return T_CRLF; } \n { return T_CRLF; } [[:blank:]] /* Skip white space */ /* Single character token */ . { return yytext[0]; } /* URI. This context scans a URI including parameters. The syntax of a URI will be checked outside the scanner */ \" { yy_push_state(C_QSTRING); } {TOKEN_SYM}({TOKEN_SYM}|[[:blank:]])*/< { yylval.yyt_str = new string(yytext); MEMMAN_NEW(yylval.yyt_str); return T_DISPLAY; } [^[:blank:]<>\r\n]+/[[:blank:]]*> { yylval.yyt_str = new string(yytext); MEMMAN_NEW(yylval.yyt_str); return T_URI; } \* { return T_URI_WILDCARD; } [^[:blank:]<>\"\r\n]+ { yylval.yyt_str = new string(yytext); MEMMAN_NEW(yylval.yyt_str); return T_URI; } [[:blank:]] /* Skip white space */ . { return yytext[0]; } \n { return T_ERROR; } /* URI special case. In several headers (eg. From, To, Contact, Reply-To) the URI can be enclosed by < and > If it is enclosed then parameters belong to the URI, if it is not enclosed then parameters belong to the header. Parameters are seperated by a semi-colon. For the URI special case, parameters belong to the header. If the parser receives a < from the scanner, then the parser will switch to the normal URI case. The syntax of a URI will be checked outside the scanner */ \" { yy_push_state(C_QSTRING); } {TOKEN_SYM}({TOKEN_SYM}|[[:blank:]])*/< { yylval.yyt_str = new string(yytext); MEMMAN_NEW(yylval.yyt_str); return T_DISPLAY; } \* { return T_URI_WILDCARD; } [^[:blank:]<>;\"\r\n]+ { yylval.yyt_str = new string(yytext); MEMMAN_NEW(yylval.yyt_str); return T_URI; } [[:blank:]] /* Skip white space */ . { return yytext[0]; } \n { return T_ERROR; } /* Quoted string (starting after open quote, closing quote will be consumed but not returned. */ \\ { yymore(); } [^\"\\\r\n]*\\\" { yymore(); } [^\"\\\r\n]*\" { yy_pop_state(); yytext[strlen(yytext)-1] = '\0'; yylval.yyt_str = new string(unescape(string(yytext))); MEMMAN_NEW(yylval.yyt_str); return T_QSTRING; } [^\"\\\n]*\n { yy_pop_state(); return T_ERROR; } . { yy_pop_state(); return T_ERROR; } /* Comment (starting after LPAREN till RPAREN) */ \\ { yymore(); } [^\(\)\\\r\n]*\\\) { yymore(); } [^\(\)\\\r\n]*\\\( { yymore(); } [^\(\)\\\r\n]*\( { t_parser::inc_comment_level(); yymore(); } [^\(\)\\\r\n]*/\) { if (t_parser::dec_comment_level()) { BEGIN(C_RPAREN); yymore(); } else { yylval.yyt_str = new string(yytext); MEMMAN_NEW(yylval.yyt_str); return T_COMMENT; } } [^\(\)\\\n]*\n { return T_ERROR; } . { return T_ERROR; } \) { BEGIN(C_COMMENT); yymore(); } /* Language tag */ {ALPHA}{1,8}(\-{ALPHA}{1,8})* { yylval.yyt_str = new string(yytext); MEMMAN_NEW(yylval.yyt_str); return T_LANG; } [[:blank:]] /* Skip white space */ . { return yytext[0]; } \r\n { return T_CRLF; } \n { return T_CRLF; } /* Word */ {WORD_SYM}+ { yylval.yyt_str = new string(yytext); MEMMAN_NEW(yylval.yyt_str); return T_WORD; } [[:blank:]] /* Skip white space */ . { return yytext[0]; } \r\n { return T_CRLF; } \n { return T_CRLF; } /* Number */ {DIGIT}+ { yylval.yyt_ulong = strtoul(yytext, NULL, 10); return T_NUM; } [[:blank:]] /* Skip white space */ . { return yytext[0]; } \r\n { return T_CRLF; } \n { return T_CRLF; } /* Date */ Mon { yylval.yyt_int = 1; return T_WKDAY; } Tue { yylval.yyt_int = 2; return T_WKDAY; } Wed { yylval.yyt_int = 3; return T_WKDAY; } Thu { yylval.yyt_int = 4; return T_WKDAY; } Fri { yylval.yyt_int = 5; return T_WKDAY; } Sat { yylval.yyt_int = 6; return T_WKDAY; } Sun { yylval.yyt_int = 0; return T_WKDAY; } Jan { yylval.yyt_int = 0; return T_MONTH; } Feb { yylval.yyt_int = 1; return T_MONTH; } Mar { yylval.yyt_int = 2; return T_MONTH; } Apr { yylval.yyt_int = 3; return T_MONTH; } May { yylval.yyt_int = 4; return T_MONTH; } Jun { yylval.yyt_int = 5; return T_MONTH; } Jul { yylval.yyt_int = 6; return T_MONTH; } Aug { yylval.yyt_int = 7; return T_MONTH; } Sep { yylval.yyt_int = 8; return T_MONTH; } Oct { yylval.yyt_int = 9; return T_MONTH; } Nov { yylval.yyt_int = 10; return T_MONTH; } Dec { yylval.yyt_int = 11; return T_MONTH; } GMT { return T_GMT; } {DIGIT}+ { yylval.yyt_ulong = strtoul(yytext, NULL, 10); return T_NUM; } [[:blank:]] /* Skip white space */ . { return yytext[0]; } \r\n { return T_CRLF; } \n { return T_CRLF; } /* Get all text till end of line */ [^\r\n]+ { yylval.yyt_str = new string(yytext); MEMMAN_NEW(yylval.yyt_str); return T_LINE; } \r\n { return T_CRLF; } \n { return T_CRLF; } \r { return T_CRLF; } /* Start of a new message */ SIP { return T_SIP; } {CAPITALS}+ { yylval.yyt_str = new string(yytext); MEMMAN_NEW(yylval.yyt_str); return T_METHOD; } [[:blank:]] /* Skip white space */ . { return T_ERROR; } \r\n { return T_CRLF; } \n { return T_CRLF; } /* Authorization scheme */ [Dd][Ii][Gg][Ee][Ss][Tt] { return T_AUTH_DIGEST; } {TOKEN_SYM}+ { yylval.yyt_str = new string(yytext); MEMMAN_NEW(yylval.yyt_str); return T_AUTH_OTHER; } [[:blank:]] /* Skip white space */ . { return T_ERROR; } \r\n { return T_CRLF; } \n { return T_CRLF; } /* IPv6 address * NOTE: the validity of the format is not checked here. */ ({HEXDIG}|[:\.])+ { yylval.yyt_str = new string(yytext); MEMMAN_NEW(yylval.yyt_str); return T_IPV6ADDR; } [[:blank:]] /* Skip white space */ . { return T_ERROR; } \r\n { return T_CRLF; } \n { return T_CRLF; } /* Parameter values may contain an IPv6 address or reference. */ ({TOKEN_SYM}|[:\[\]])+ { yylval.yyt_str = new string(yytext); MEMMAN_NEW(yylval.yyt_str); return T_PARAMVAL; } \" { yy_push_state(C_QSTRING); } [[:blank:]] /* Skip white space */ . { return T_ERROR; } \r\n { return T_CRLF; } \n { return T_CRLF; } twinkle-1.10.1/src/parser/sip_body.cpp000066400000000000000000000162461277565361200176750ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "sip_body.h" #include #include #include #include "log.h" #include "protocol.h" #include "sip_message.h" #include "util.h" #include "audits/memman.h" #include "audio/rtp_telephone_event.h" //////////////////////////////////// // class t_sip_body //////////////////////////////////// t_sip_body::t_sip_body() { invalid = false; } bool t_sip_body::local_ip_check(void) const { return true; } size_t t_sip_body::get_encoded_size(void) const { return encode().size(); } //////////////////////////////////// // class t_sip_xml_body //////////////////////////////////// void t_sip_body_xml::create_xml_doc(const string &xml_version, const string &charset) { clear_xml_doc(); // XML doc xml_doc = xmlNewDoc(BAD_CAST xml_version.c_str()); MEMMAN_NEW(xml_doc); xml_doc->encoding = xmlCharStrdup(charset.c_str()); } void t_sip_body_xml::clear_xml_doc(void) { if (xml_doc) { MEMMAN_DELETE(xml_doc); xmlFreeDoc(xml_doc); xml_doc = NULL; } } void t_sip_body_xml::copy_xml_doc(t_sip_body_xml *to_body) const { if (to_body->xml_doc) { to_body->clear_xml_doc(); } if (xml_doc) { to_body->xml_doc = xmlCopyDoc(xml_doc, 1); if (!to_body->xml_doc) { log_file->write_report("Failed to copy xml document.", "t_sip_body_xml::copy", LOG_NORMAL, LOG_CRITICAL); } else { MEMMAN_NEW(to_body->xml_doc); } } } t_sip_body_xml::t_sip_body_xml() : t_sip_body(), xml_doc(NULL) {} t_sip_body_xml::~t_sip_body_xml() { clear_xml_doc(); } string t_sip_body_xml::encode(void) const { if (!xml_doc) { t_sip_body_xml *self = const_cast(this); self->create_xml_doc(); } assert(xml_doc); xmlChar *buf; int buf_size; xmlDocDumpMemory(xml_doc, &buf, &buf_size); string result((char*)buf); xmlFree(buf); return result; } bool t_sip_body_xml::parse(const string &s) { assert(xml_doc == NULL); xml_doc = xmlReadMemory(s.c_str(), s.size(), "noname.xml", NULL, XML_PARSE_NOBLANKS | XML_PARSE_NOERROR | XML_PARSE_NOWARNING); if (!xml_doc) { log_file->write_report("Failed to parse xml document.", "t_sip_body_xml::parse", LOG_NORMAL, LOG_WARNING); } else { MEMMAN_NEW(xml_doc); } return (xml_doc != NULL); } //////////////////////////////////// // class t_sip_body_opaque //////////////////////////////////// t_sip_body_opaque::t_sip_body_opaque() : t_sip_body() {} t_sip_body_opaque::t_sip_body_opaque(string s) : t_sip_body(), opaque(s) {} string t_sip_body_opaque::encode(void) const { return opaque; } t_sip_body *t_sip_body_opaque::copy(void) const { t_sip_body_opaque *sb = new t_sip_body_opaque(*this); MEMMAN_NEW(sb); return sb; } t_body_type t_sip_body_opaque::get_type(void) const { return BODY_OPAQUE; } t_media t_sip_body_opaque::get_media(void) const { return t_media("application", "octet-stream"); } size_t t_sip_body_opaque::get_encoded_size(void) const { return opaque.size(); } //////////////////////////////////// // class t_sip_body_sipfrag //////////////////////////////////// t_sip_body_sipfrag::t_sip_body_sipfrag(t_sip_message *m) : t_sip_body() { sipfrag = m->copy(); } t_sip_body_sipfrag::~t_sip_body_sipfrag() { MEMMAN_DELETE(sipfrag); delete sipfrag; } string t_sip_body_sipfrag::encode(void) const { return sipfrag->encode(false); } t_sip_body *t_sip_body_sipfrag::copy(void) const { t_sip_body_sipfrag *sb = new t_sip_body_sipfrag(sipfrag); MEMMAN_NEW(sb); return sb; } t_body_type t_sip_body_sipfrag::get_type(void) const { return BODY_SIPFRAG; } t_media t_sip_body_sipfrag::get_media(void) const { return t_media("message", "sipfrag"); } //////////////////////////////////// // class t_sip_body_dtmf_relay //////////////////////////////////// t_sip_body_dtmf_relay::t_sip_body_dtmf_relay() : t_sip_body() { signal = '0'; duration = 250; } t_sip_body_dtmf_relay::t_sip_body_dtmf_relay(char _signal, uint16 _duration) : signal(_signal), duration(_duration) {} string t_sip_body_dtmf_relay::encode(void) const { string s = "Signal="; s += signal; s += CRLF; s += "Duration="; s += int2str(duration); s += CRLF; return s; } t_sip_body *t_sip_body_dtmf_relay::copy(void) const { t_sip_body_dtmf_relay *sb = new t_sip_body_dtmf_relay(*this); MEMMAN_NEW(sb); return sb; } t_body_type t_sip_body_dtmf_relay::get_type(void) const { return BODY_DTMF_RELAY; } t_media t_sip_body_dtmf_relay::get_media(void) const { return t_media("application", "dtmf-relay"); } bool t_sip_body_dtmf_relay::parse(const string &s) { signal = 0; duration = 250; bool valid = false; vector lines = split_linebreak(s); for (vector::iterator i = lines.begin(); i != lines.end(); i++) { string line = trim(*i); if (line.empty()) continue; vector l = split_on_first(line, '='); if (l.size() != 2) continue; string parameter = tolower(trim(l[0])); string value = tolower(trim(l[1])); if (value.empty()) continue; if (parameter == "signal") { if (!is_valid_dtmf_sym(value[0])) return false; signal = value[0]; valid = true; } else if (parameter == "duration") { duration = atoi(value.c_str()); if (duration == 0) return false; } } return valid; } //////////////////////////////////// // class t_sip_body_plain_text //////////////////////////////////// t_sip_body_plain_text::t_sip_body_plain_text() : t_sip_body() {} t_sip_body_plain_text::t_sip_body_plain_text(const string &_text) : t_sip_body(), text(_text) {} string t_sip_body_plain_text::encode(void) const { return text; } t_sip_body *t_sip_body_plain_text::copy(void) const { t_sip_body *sb = new t_sip_body_plain_text(*this); MEMMAN_NEW(sb); return sb; } t_body_type t_sip_body_plain_text::get_type(void) const { return BODY_PLAIN_TEXT; } t_media t_sip_body_plain_text::get_media(void) const { return t_media("text", "plain"); } size_t t_sip_body_plain_text::get_encoded_size(void) const { return text.size(); } //////////////////////////////////// // class t_sip_body_html_text //////////////////////////////////// t_sip_body_html_text::t_sip_body_html_text(const string &_text) : t_sip_body(), text(_text) {} string t_sip_body_html_text::encode(void) const { return text; } t_sip_body *t_sip_body_html_text::copy(void) const { t_sip_body *sb = new t_sip_body_html_text(*this); MEMMAN_NEW(sb); return sb; } t_body_type t_sip_body_html_text::get_type(void) const { return BODY_HTML_TEXT; } t_media t_sip_body_html_text::get_media(void) const { return t_media("text", "html"); } size_t t_sip_body_html_text::get_encoded_size(void) const { return text.size(); } twinkle-1.10.1/src/parser/sip_body.h000066400000000000000000000151131277565361200173320ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // SIP bodies #ifndef _H_SIP_BODY #define _H_SIP_BODY #include #include #include #include "media_type.h" //@{ /** @name Utilies for XML body parsing */ /** * Check the tag name of an XML node. * @param node [in] (xmlNode *) The XML node to check. * @param tag [in] (const char *) The tag name. * @param namespace [in] (const char *) The namespace of the tag. * @return true if the node has the tag name within the name space. */ #define IS_XML_TAG(node, tag, namespace)\ ((node)->type == XML_ELEMENT_NODE &&\ (node)->ns &&\ xmlStrEqual((node)->ns->href, BAD_CAST (namespace)) &&\ xmlStrEqual((node)->name, BAD_CAST (tag))) /** * Check the attribute name of an XML attribute. */ #define IS_XML_ATTR(attr, attr_name, namespace)\ ((attr)->type == XML_ATTRIBUTE_NODE &&\ (attr)->ns &&\ xmlStrEqual((attr)->ns->href, BAD_CAST (namespace)) &&\ xmlStrEqual((attr)->name, BAD_CAST (attr_name))) //@} class t_sip_message; using namespace std; /** Body type. */ enum t_body_type { BODY_OPAQUE, /**< Opaque body. */ BODY_SDP, /**< SDP */ BODY_SIPFRAG, /**< message/sipfrag RFC 3420 */ BODY_DTMF_RELAY, /**< DTMF relay as defined by Cisco */ BODY_SIMPLE_MSG_SUM, /**< Simple message summary RFC 3842 */ BODY_PLAIN_TEXT, /**< Plain text for messaging */ BODY_HTML_TEXT, /**< HTML text for messaging */ BODY_PIDF_XML, /**< pidf+xml RFC 3863 */ BODY_IM_ISCOMPOSING_XML /**< im-iscomposing+xml RFC 3994 */ }; /** Abstract base class for SIP bodies. */ class t_sip_body { public: /** * Indicates if the body content is invalid. * This will be set by the body parser. */ bool invalid; /** Constructor. */ t_sip_body(); virtual ~t_sip_body() {} /** * Encode the body. * @return Text encoded body. */ virtual string encode(void) const = 0; /** * Create a copy of the body. * @return Copy of the body. */ virtual t_sip_body *copy(void) const = 0; /** * Get type of body. * @return body type. */ virtual t_body_type get_type(void) const = 0; /** * Get content type for this type of body. * @return Content type. */ virtual t_media get_media(void) const = 0; /** * Check if all local IP address are correctly filled in. This * check is an integrity check to help debugging the auto IP * discover feature. */ virtual bool local_ip_check(void) const; /** * Return the size of the encoded body. This method encodes the body * to calculate the size. When a more efficient algorithm is available * a sub class may override this method. * @return The size of the encoded body in bytes. */ virtual size_t get_encoded_size(void) const; }; /** Abstract base class for XML formatted bodies. */ class t_sip_body_xml : public t_sip_body { protected: xmlDoc *xml_doc; /**< XML document */ /** * Create an empty XML document. * Override this method to create the specific XML document. * @param xml_version [in] The XML version of the document. * @param charset [in] The character set of the document. */ virtual void create_xml_doc(const string &xml_version = "1.0", const string &charset = "UTF-8"); /** Remove the XML document */ virtual void clear_xml_doc(void); /** * Copy the XML document from this body to another body. * @param to_body [in] The body to copy the XML body to. */ virtual void copy_xml_doc(t_sip_body_xml *to_body) const; public: /** Constructor */ t_sip_body_xml(); /** Destructor */ virtual ~t_sip_body_xml(); virtual string encode(void) const; /** * Parse a text representation of the body. * The result is stored in @ref xml_doc * @param s [in] Text to parse. * @return True if parsing and state extracting succeeded, false otherwise. * @pre xml_doc == NULL * @post If parsing succeeds then xml_doc != NULL */ virtual bool parse(const string &s); }; /** * This body can contain any type of body. The contents are * unparsed and thus opaque. */ class t_sip_body_opaque : public t_sip_body { public: string opaque; /**< The body contents. */ /** Construct body with empty content. */ t_sip_body_opaque(); /** * Construct a body with opaque content. * @param s [in] The content. */ t_sip_body_opaque(string s); string encode(void) const; t_sip_body *copy(void) const; t_body_type get_type(void) const; t_media get_media(void) const; virtual size_t get_encoded_size(void) const; }; // RFC 3420 // sipfrag body class t_sip_body_sipfrag : public t_sip_body { public: t_sip_message *sipfrag; t_sip_body_sipfrag(t_sip_message *m); ~t_sip_body_sipfrag(); string encode(void) const; t_sip_body *copy(void) const; t_body_type get_type(void) const; t_media get_media(void) const; }; // application/dtmf-relay body class t_sip_body_dtmf_relay : public t_sip_body { public: char signal; uint16 duration; // ms t_sip_body_dtmf_relay(); t_sip_body_dtmf_relay(char _signal, uint16 _duration); string encode(void) const; t_sip_body *copy(void) const; t_body_type get_type(void) const; t_media get_media(void) const; bool parse(const string &s); }; /** Plain text body. */ class t_sip_body_plain_text : public t_sip_body { public: string text; /**< The text */ /** Construct a body with empty text. */ t_sip_body_plain_text(); /** * Constructor. * @param _text [in] The body text. */ t_sip_body_plain_text(const string &_text); string encode(void) const; t_sip_body *copy(void) const; t_body_type get_type(void) const; t_media get_media(void) const; virtual size_t get_encoded_size(void) const; }; /** Html text body. */ class t_sip_body_html_text : public t_sip_body { public: string text; /**< The text */ /** * Constructor. * @param _text [in] The body text. */ t_sip_body_html_text(const string &_text); string encode(void) const; t_sip_body *copy(void) const; t_body_type get_type(void) const; t_media get_media(void) const; virtual size_t get_encoded_size(void) const; }; #endif twinkle-1.10.1/src/parser/sip_message.cpp000066400000000000000000000314301277565361200203540ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include "sip_message.h" #include "util.h" #include "parse_ctrl.h" #include "sdp/sdp.h" #include "audits/memman.h" //////////////////////////////////// // class t_sip_message //////////////////////////////////// t_sip_message::t_sip_message() : local_ip_(0) { src_ip_port.clear(); version = SIP_VERSION; body = NULL; } t_sip_message::t_sip_message(const t_sip_message& m) : local_ip_(m.local_ip_), src_ip_port(m.src_ip_port), version(m.version), hdr_accept(m.hdr_accept), hdr_accept_encoding(m.hdr_accept_encoding), hdr_accept_language(m.hdr_accept_language), hdr_alert_info(m.hdr_alert_info), hdr_allow(m.hdr_allow), hdr_allow_events(m.hdr_allow_events), hdr_auth_info(m.hdr_auth_info), hdr_authorization(m.hdr_authorization), hdr_call_id(m.hdr_call_id), hdr_call_info(m.hdr_call_info), hdr_contact(m.hdr_contact), hdr_content_disp(m.hdr_content_disp), hdr_content_encoding(m.hdr_content_encoding), hdr_content_language(m.hdr_content_language), hdr_content_length(m.hdr_content_length), hdr_content_type(m.hdr_content_type), hdr_cseq(m.hdr_cseq), hdr_date(m.hdr_date), hdr_error_info(m.hdr_error_info), hdr_event(m.hdr_event), hdr_expires(m.hdr_expires), hdr_from(m.hdr_from), hdr_in_reply_to(m.hdr_in_reply_to), hdr_max_forwards(m.hdr_max_forwards), hdr_min_expires(m.hdr_min_expires), hdr_mime_version(m.hdr_mime_version), hdr_organization(m.hdr_organization), hdr_p_asserted_identity(m.hdr_p_asserted_identity), hdr_p_preferred_identity(m.hdr_p_preferred_identity), hdr_priority(m.hdr_priority), hdr_privacy(m.hdr_privacy), hdr_proxy_authenticate(m.hdr_proxy_authenticate), hdr_proxy_authorization(m.hdr_proxy_authorization), hdr_proxy_require(m.hdr_proxy_require), hdr_rack(m.hdr_rack), hdr_record_route(m.hdr_record_route), hdr_refer_sub(m.hdr_refer_sub), hdr_refer_to(m.hdr_refer_to), hdr_referred_by(m.hdr_referred_by), hdr_replaces(m.hdr_replaces), hdr_reply_to(m.hdr_reply_to), hdr_require(m.hdr_require), hdr_request_disposition(m.hdr_request_disposition), hdr_retry_after(m.hdr_retry_after), hdr_route(m.hdr_route), hdr_rseq(m.hdr_rseq), hdr_server(m.hdr_server), hdr_service_route(m.hdr_service_route), hdr_sip_etag(m.hdr_sip_etag), hdr_sip_if_match(m.hdr_sip_if_match), hdr_subject(m.hdr_subject), hdr_subscription_state(m.hdr_subscription_state), hdr_supported(m.hdr_supported), hdr_timestamp(m.hdr_timestamp), hdr_to(m.hdr_to), hdr_unsupported(m.hdr_unsupported), hdr_user_agent(m.hdr_user_agent), hdr_via(m.hdr_via), hdr_warning(m.hdr_warning), hdr_www_authenticate(m.hdr_www_authenticate), unknown_headers(m.unknown_headers) { if (m.body) { body = m.body->copy(); } else { body = NULL; } } t_sip_message::~t_sip_message() { if (body) { MEMMAN_DELETE(body); delete body; } } t_msg_type t_sip_message::get_type(void) const { return MSG_SIPFRAG; } void t_sip_message::add_unknown_header(const string &name, const string &value) { t_parameter h(name, value); unknown_headers.push_back(h); } bool t_sip_message::is_valid(bool &fatal, string &reason) const { // RFC 3261 8.1.1 // Mandatory headers if (!hdr_to.is_populated()) { fatal = true; reason = "To-header missing"; return false; } if (!hdr_from.is_populated()) { fatal = true; reason = "From header missing"; return false; } if (!hdr_cseq.is_populated()) { fatal = true; reason = "CSeq header missing"; return false; } if (!hdr_call_id.is_populated()) { fatal = true; reason = "Call-ID header missing"; return false; } if (!hdr_via.is_populated()) { fatal = true; reason = "Via header missing"; return false; } // RFC 3261 20.15 // Content-Type MUST be present if body is not empty if (body && !hdr_content_type.is_populated()) { fatal = false; reason = "Content-Type header missing"; return false; } // RFC 3261 18.4 // The Content-Length header field MUST be used with stream oriented transports. if (cmp_nocase(hdr_via.via_list.front().transport, "tcp") == 0 && !hdr_content_length.is_populated()) { fatal = false; reason = "Content-Length header missing"; return false; } return true; } string t_sip_message::encode(bool add_content_length) { string s; string encoded_body; // RFC 3261 7.3.1 // Headers needed by a proxy should be on top s += hdr_via.encode(); s += hdr_route.encode(); s += hdr_record_route.encode(); s += hdr_service_route.encode(); s += hdr_proxy_require.encode(); s += hdr_max_forwards.encode(); s += hdr_proxy_authenticate.encode(); s += hdr_proxy_authorization.encode(); // Order as in many examples s += hdr_to.encode(); s += hdr_from.encode(); s += hdr_call_id.encode(); s += hdr_cseq.encode(); s += hdr_contact.encode(); s += hdr_content_type.encode(); // Privacy related headers s += hdr_privacy.encode(); s += hdr_p_asserted_identity.encode(); s += hdr_p_preferred_identity.encode(); // Authentication headers s += hdr_auth_info.encode(); s += hdr_authorization.encode(); s += hdr_www_authenticate.encode(); // Remaining headers in alphabetical order s += hdr_accept.encode(); s += hdr_accept_encoding.encode(); s += hdr_accept_language.encode(); s += hdr_alert_info.encode(); s += hdr_allow.encode(); s += hdr_allow_events.encode(); s += hdr_call_info.encode(); s += hdr_content_disp.encode(); s += hdr_content_encoding.encode(); s += hdr_content_language.encode(); s += hdr_date.encode(); s += hdr_error_info.encode(); s += hdr_event.encode(); s += hdr_expires.encode(); s += hdr_in_reply_to.encode(); s += hdr_min_expires.encode(); s += hdr_mime_version.encode(); s += hdr_organization.encode(); s += hdr_priority.encode(); s += hdr_rack.encode(); s += hdr_refer_sub.encode(); s += hdr_refer_to.encode(); s += hdr_referred_by.encode(); s += hdr_replaces.encode(); s += hdr_reply_to.encode(); s += hdr_require.encode(); s += hdr_request_disposition.encode(); s += hdr_retry_after.encode(); s += hdr_rseq.encode(); s += hdr_server.encode(); s += hdr_sip_etag.encode(); s += hdr_sip_if_match.encode(); s += hdr_subject.encode(); s += hdr_subscription_state.encode(); s += hdr_supported.encode(); s += hdr_timestamp.encode(); s += hdr_unsupported.encode(); s += hdr_user_agent.encode(); s += hdr_warning.encode(); // Unknown headers for (list::const_iterator i = unknown_headers.begin(); i != unknown_headers.end(); i++) { s += i->name; s += ": "; s += i->value; s += CRLF; } // Encode body if present. Set the content length. if (body) { encoded_body = body->encode(); hdr_content_length.set_length(encoded_body.size()); } else { // RFC 3261 20.14 // If no body is present then Content-Length MUST be 0 hdr_content_length.set_length(0); } // Content-Length appears last in examples if (add_content_length) { s += hdr_content_length.encode(); } // Blank line between headers and body s += CRLF; // Add body if (body) s += encoded_body; return s; } list t_sip_message::encode_env(void) { list l; // RFC 3261 7.3.1 // Headers needed by a proxy should be on top l.push_back(hdr_via.encode_env()); l.push_back(hdr_route.encode_env()); l.push_back(hdr_record_route.encode_env()); l.push_back(hdr_service_route.encode_env()); l.push_back(hdr_proxy_require.encode_env()); l.push_back(hdr_max_forwards.encode_env()); l.push_back(hdr_proxy_authenticate.encode_env()); l.push_back(hdr_proxy_authorization.encode_env()); // Order as in many examples l.push_back(hdr_to.encode_env()); l.push_back(hdr_from.encode_env()); l.push_back(hdr_call_id.encode_env()); l.push_back(hdr_cseq.encode_env()); l.push_back(hdr_contact.encode_env()); l.push_back(hdr_content_type.encode_env()); // Authentication headers l.push_back(hdr_auth_info.encode_env()); l.push_back(hdr_authorization.encode_env()); l.push_back(hdr_www_authenticate.encode_env()); // Authentication headers l.push_back(hdr_auth_info.encode_env()); l.push_back(hdr_authorization.encode_env()); l.push_back(hdr_www_authenticate.encode_env()); // Remaining headers in alphabetical order l.push_back(hdr_accept.encode_env()); l.push_back(hdr_accept_encoding.encode_env()); l.push_back(hdr_accept_language.encode_env()); l.push_back(hdr_alert_info.encode_env()); l.push_back(hdr_allow.encode_env()); l.push_back(hdr_allow_events.encode_env()); l.push_back(hdr_call_info.encode_env()); l.push_back(hdr_content_disp.encode_env()); l.push_back(hdr_content_encoding.encode_env()); l.push_back(hdr_content_language.encode_env()); l.push_back(hdr_date.encode_env()); l.push_back(hdr_error_info.encode_env()); l.push_back(hdr_event.encode_env()); l.push_back(hdr_expires.encode_env()); l.push_back(hdr_in_reply_to.encode_env()); l.push_back(hdr_min_expires.encode_env()); l.push_back(hdr_mime_version.encode_env()); l.push_back(hdr_organization.encode_env()); l.push_back(hdr_priority.encode_env()); l.push_back(hdr_rack.encode_env()); l.push_back(hdr_refer_sub.encode_env()); l.push_back(hdr_refer_to.encode_env()); l.push_back(hdr_referred_by.encode_env()); l.push_back(hdr_replaces.encode_env()); l.push_back(hdr_reply_to.encode_env()); l.push_back(hdr_require.encode_env()); l.push_back(hdr_request_disposition.encode_env()); l.push_back(hdr_retry_after.encode_env()); l.push_back(hdr_rseq.encode_env()); l.push_back(hdr_server.encode_env()); l.push_back(hdr_sip_etag.encode_env()); l.push_back(hdr_sip_if_match.encode_env()); l.push_back(hdr_subject.encode_env()); l.push_back(hdr_subscription_state.encode_env()); l.push_back(hdr_supported.encode_env()); l.push_back(hdr_timestamp.encode_env()); l.push_back(hdr_unsupported.encode_env()); l.push_back(hdr_user_agent.encode_env()); l.push_back(hdr_warning.encode_env()); // Unknown headers for (list::const_iterator i = unknown_headers.begin(); i != unknown_headers.end(); i++) { string s = "SIP_"; s += toupper(replace_char(i->name, '-', '_')); s += '='; s += i->value; l.push_back(s); } l.push_back(hdr_content_length.encode_env()); return l; } t_sip_message *t_sip_message::copy(void) const { t_sip_message *m = new t_sip_message(*this); MEMMAN_NEW(m); return m; } void t_sip_message::set_body_plain_text(const string &text, const string &charset) { // Content-Type header t_media mime_type("text", "plain"); mime_type.charset = charset; hdr_content_type.set_media(mime_type); if (body) { MEMMAN_DELETE(body); delete body; } body = new t_sip_body_plain_text(text); MEMMAN_NEW(body); } bool t_sip_message::set_body_from_file(const string &filename, const t_media &media) { // Open file and set read pointer at end so we know the size. ifstream f(filename.c_str(), ios::binary); if (!f) return false; ostringstream body_stream(ios::binary); // Copy file into body body_stream << f.rdbuf(); if (!f.good() || !body_stream.good()) { return false; } // Create body of correct type t_sip_body *new_body = NULL; if (media.type == "text" && media.subtype == "plain") { t_sip_body_plain_text *text_body = new t_sip_body_plain_text(body_stream.str()); MEMMAN_NEW(text_body); new_body = text_body; } else { t_sip_body_opaque *opaque_body = new t_sip_body_opaque(body_stream.str()); MEMMAN_NEW(opaque_body); new_body = opaque_body; } if (body) { MEMMAN_DELETE(body); delete body; } body = new_body; // Content-Type header hdr_content_type.set_media(media); return true; } size_t t_sip_message::get_encoded_size(void) { string s = encode(); return s.size(); } bool t_sip_message::local_ip_check(void) const { if (get_type() == MSG_REQUEST && hdr_via.is_populated()) { const t_via &v = hdr_via.via_list.front(); if (v.host == "0.0.0.0") return false; } if (hdr_contact.is_populated()) { if (!hdr_contact.any_flag && !hdr_contact.contact_list.empty()) { const t_contact_param &c = hdr_contact.contact_list.front(); if (c.uri.get_host() == "0.0.0.0") return false; } } if (body) { return body->local_ip_check(); } return true; } void t_sip_message::calc_local_ip(void) { // Do nothing } unsigned long t_sip_message::get_local_ip(void) { if (local_ip_ == 0) calc_local_ip(); return local_ip_; } twinkle-1.10.1/src/parser/sip_message.h000066400000000000000000000201541277565361200200220ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // SIP message #ifndef _H_SIP_MESSAGE #define _H_SIP_MESSAGE #include #include #include "definitions.h" #include "hdr_accept.h" #include "hdr_accept_encoding.h" #include "hdr_accept_language.h" #include "hdr_alert_info.h" #include "hdr_allow.h" #include "hdr_allow_events.h" #include "hdr_auth_info.h" #include "hdr_authorization.h" #include "hdr_call_id.h" #include "hdr_call_info.h" #include "hdr_contact.h" #include "hdr_content_disp.h" #include "hdr_content_encoding.h" #include "hdr_content_language.h" #include "hdr_content_length.h" #include "hdr_content_type.h" #include "hdr_cseq.h" #include "hdr_date.h" #include "hdr_error_info.h" #include "hdr_event.h" #include "hdr_expires.h" #include "hdr_from.h" #include "hdr_in_reply_to.h" #include "hdr_max_forwards.h" #include "hdr_min_expires.h" #include "hdr_mime_version.h" #include "hdr_organization.h" #include "hdr_p_asserted_identity.h" #include "hdr_p_preferred_identity.h" #include "hdr_priority.h" #include "hdr_privacy.h" #include "hdr_proxy_authenticate.h" #include "hdr_proxy_authorization.h" #include "hdr_proxy_require.h" #include "hdr_rack.h" #include "hdr_record_route.h" #include "hdr_refer_sub.h" #include "hdr_refer_to.h" #include "hdr_referred_by.h" #include "hdr_replaces.h" #include "hdr_reply_to.h" #include "hdr_require.h" #include "hdr_request_disposition.h" #include "hdr_retry_after.h" #include "hdr_route.h" #include "hdr_rseq.h" #include "hdr_server.h" #include "hdr_service_route.h" #include "hdr_sip_etag.h" #include "hdr_sip_if_match.h" #include "hdr_subject.h" #include "hdr_subscription_state.h" #include "hdr_supported.h" #include "hdr_timestamp.h" #include "hdr_to.h" #include "hdr_unsupported.h" #include "hdr_user_agent.h" #include "hdr_via.h" #include "hdr_warning.h" #include "hdr_www_authenticate.h" #include "parameter.h" #include "sip_body.h" // Macro's to access the body of a message, eg msg.sdp_body #define SDP_BODY ((t_sdp *)body) #define OPAQUE_BODY ((t_sip_body_opaque)*body) using namespace std; enum t_msg_type { MSG_REQUEST, MSG_RESPONSE, MSG_SIPFRAG, // Only a sequence of headers (RFC 3420) }; class t_sip_message { protected: /** * Local IP address that will be uses for this SIP message. * The local IP address can only be determined when the destination * of a SIP message is known (because of multi homing). */ unsigned long local_ip_; public: // The source IP address and port are only set for messages // received from the network. So the transaction user knows // where a message comes from. t_ip_port src_ip_port; // SIP version string version; // All possible headers t_hdr_accept hdr_accept; t_hdr_accept_encoding hdr_accept_encoding; t_hdr_accept_language hdr_accept_language; t_hdr_alert_info hdr_alert_info; t_hdr_allow hdr_allow; t_hdr_allow_events hdr_allow_events; t_hdr_auth_info hdr_auth_info; t_hdr_authorization hdr_authorization; t_hdr_call_id hdr_call_id; t_hdr_call_info hdr_call_info; t_hdr_contact hdr_contact; t_hdr_content_disp hdr_content_disp; t_hdr_content_encoding hdr_content_encoding; t_hdr_content_language hdr_content_language; t_hdr_content_length hdr_content_length; t_hdr_content_type hdr_content_type; t_hdr_cseq hdr_cseq; t_hdr_date hdr_date; t_hdr_error_info hdr_error_info; t_hdr_event hdr_event; t_hdr_expires hdr_expires; t_hdr_from hdr_from; t_hdr_in_reply_to hdr_in_reply_to; t_hdr_max_forwards hdr_max_forwards; t_hdr_min_expires hdr_min_expires; t_hdr_mime_version hdr_mime_version; t_hdr_organization hdr_organization; t_hdr_p_asserted_identity hdr_p_asserted_identity; t_hdr_p_preferred_identity hdr_p_preferred_identity; t_hdr_priority hdr_priority; t_hdr_privacy hdr_privacy; t_hdr_proxy_authenticate hdr_proxy_authenticate; t_hdr_proxy_authorization hdr_proxy_authorization; t_hdr_proxy_require hdr_proxy_require; t_hdr_rack hdr_rack; t_hdr_record_route hdr_record_route; t_hdr_refer_sub hdr_refer_sub; t_hdr_refer_to hdr_refer_to; t_hdr_referred_by hdr_referred_by; t_hdr_replaces hdr_replaces; t_hdr_reply_to hdr_reply_to; t_hdr_require hdr_require; t_hdr_request_disposition hdr_request_disposition; t_hdr_retry_after hdr_retry_after; t_hdr_route hdr_route; t_hdr_rseq hdr_rseq; t_hdr_server hdr_server; t_hdr_service_route hdr_service_route; t_hdr_sip_etag hdr_sip_etag; t_hdr_sip_if_match hdr_sip_if_match; t_hdr_subject hdr_subject; t_hdr_subscription_state hdr_subscription_state; t_hdr_supported hdr_supported; t_hdr_timestamp hdr_timestamp; t_hdr_to hdr_to; t_hdr_unsupported hdr_unsupported; t_hdr_user_agent hdr_user_agent; t_hdr_via hdr_via; t_hdr_warning hdr_warning; t_hdr_www_authenticate hdr_www_authenticate; // Unknown headers are represented by parameters. // Parameter.name = header name // Parameter.value = header value list unknown_headers; // A SIP message can carry a body t_sip_body *body; t_sip_message(); t_sip_message(const t_sip_message& m); virtual ~t_sip_message(); virtual t_msg_type get_type(void) const; void add_unknown_header(const string &name, const string &value); // Check if the message is valid. At this class the // general rules applying to both requests and responses // is checked. // fatal is true if one of the headers mandatory for all // messages is missing (to, from, cseq, call-id, via). // reason contains a reason string if the message is invalid. virtual bool is_valid(bool &fatal, string &reason) const; // Return encoded headers // The version should be encode by the subclasses. // Parameter add_content_length indicates if a Content-Length // header must be added. Usually it must, only for sipfrag bodies // it may be omitted. virtual string encode(bool add_content_length = true); // Return list of environment variable settings for all headers // (see header.h for the format) // Besides the header variables the following variables will be // returned as well: // // SIP_REQUEST_METHOD, for a request // SIP_REQUEST_URI, for a request // SIP_STATUS_CODE, for a response // SIP_STATUS_REASON, for a response virtual list encode_env(void); // Create a copy of the message virtual t_sip_message *copy(void) const; /** * Set a plain text body in the message. * @param text [in] The text. * @param charset [in] The character set used for encoding. * @post The Content-Type header is set to "text/plain". * @post If a body was already present then it is deleted. */ void set_body_plain_text(const string &text, const string &charset); /** * Set a body with the contents of a file. * @param filename [in] The name of the file. * @param media [in] The mime type of the contents. * @return True of body is set, false if file could not be read. */ bool set_body_from_file(const string &filename, const t_media &media); /** * Get the size of an encoded SIP message. * @return Size in bytes. */ size_t get_encoded_size(void); /** * Check if all local IP address are correctly filled in. This * check is an integrity check to help debugging the auto IP * discover feature. */ bool local_ip_check(void) const; /** Determine the local IP address for this SIP message. */ virtual void calc_local_ip(void); /** * Get the local IP address for this SIP message. * The local IP address can be used as source address for sending * the message. * @return The local IP address. * @return 0, if the local IP address is not determined yet. */ unsigned long get_local_ip(void); }; #endif twinkle-1.10.1/src/patterns/000077500000000000000000000000001277565361200157145ustar00rootroot00000000000000twinkle-1.10.1/src/patterns/CMakeLists.txt000066400000000000000000000002201277565361200204460ustar00rootroot00000000000000project(libtwinkle-patterns) set(LIBTWINKLE_PATTERNS-SRCS observer.cpp ) add_library(libtwinkle-patterns OBJECT ${LIBTWINKLE_PATTERNS-SRCS}) twinkle-1.10.1/src/patterns/observer.cpp000066400000000000000000000026331277565361200202530ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "observer.h" using namespace patterns; t_subject::~t_subject() { mtx_observers.lock(); for (list::const_iterator it = observers.begin(); it != observers.end(); ++it) { (*it)->subject_destroyed(); } mtx_observers.unlock(); } void t_subject::attach(t_observer *o) { mtx_observers.lock(); observers.push_back(o); mtx_observers.unlock(); } void t_subject::detach(t_observer *o) { mtx_observers.lock(); observers.remove(o); mtx_observers.unlock(); } void t_subject::notify(void) const { mtx_observers.lock(); for (list::const_iterator it = observers.begin(); it != observers.end(); ++it) { (*it)->update(); } mtx_observers.unlock(); } twinkle-1.10.1/src/patterns/observer.h000066400000000000000000000033411277565361200177150ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Abstract observer pattern. */ #ifndef _OBSERVER_H #define _OBSERVER_H #include #include "threads/mutex.h" using namespace std; namespace patterns { /** Observer. */ class t_observer { public: virtual ~t_observer() {}; /** * This method is called by an observed subject to indicate its state * has changed. */ virtual void update(void) = 0; /** * This method is called when the subject is destroyed. */ virtual void subject_destroyed(void) = 0; }; /** An observed subject. */ class t_subject { private: /** Mutex to protect access to the observers. */ mutable t_recursive_mutex mtx_observers; list observers; /** Observers of this subject. */ public: virtual ~t_subject(); /** * Attach an observer. * @param o [in] The observer. */ void attach(t_observer *o); /** * Detach an observer. * @param o [in] The observer. */ void detach(t_observer *o); /** * Notify all observers. */ void notify(void) const; }; }; // namespace #endif twinkle-1.10.1/src/phone.cpp000066400000000000000000002662051277565361200157040ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include #include "call_history.h" #include "call_script.h" #include "exceptions.h" #include "phone.h" #include "line.h" #include "log.h" #include "sdp/sdp.h" #include "translator.h" #include "util.h" #include "user.h" #include "userintf.h" #include "audits/memman.h" #include "parser/parse_ctrl.h" #include "sockets/socket.h" #include "stun/stun_transaction.h" extern t_phone *phone; extern t_event_queue *evq_timekeeper; extern string user_host; // t_transfer_data t_transfer_data::t_transfer_data(t_request *r, unsigned short _lineno, bool _hide_user, t_phone_user *pu) : refer_request(dynamic_cast(r->copy())), lineno(_lineno), hide_user(_hide_user), phone_user(pu) {} t_transfer_data::~t_transfer_data() { MEMMAN_DELETE(refer_request); delete refer_request; } t_request *t_transfer_data::get_refer_request(void) const { return refer_request; } bool t_transfer_data::get_hide_user(void) const { return hide_user; } unsigned short t_transfer_data::get_lineno(void) const { return lineno; } t_phone_user *t_transfer_data::get_phone_user(void) const { return phone_user; } // t_phone /////////// // Private /////////// void t_phone::move_line_to_background(unsigned short lineno) { // R/W lock is held by callers // The line will be released in the background. It should // immediately release its RTP ports as these maybe needed // for new calls. lines.at(lineno)->kill_rtp(); cleanup_3way_state(lineno); // Move the line to the back of the vector. lines.push_back(lines.at(lineno)); lines.back()->line_number = lines.size() - 1; // Create a new line for making calls. lines.at(lineno) = new t_line(this, lineno); MEMMAN_NEW(lines.at(lineno)); // The new line must have the same RTP port as the // releasing line, otherwise it may conflict with // the other lines. Due to call transfers, the port // number may be unrelated to the line position. lines.at(lineno)->rtp_port = lines.back()->get_rtp_port(); log_file->write_header("t_phone::move_line_to_background", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Moved line "); log_file->write_raw(lineno + 1); log_file->write_raw(" to background position "); log_file->write_raw(lines.back()->get_line_number() + 1); log_file->write_endl(); log_file->write_footer(); // Notify the user interface of the line state change ui->cb_line_state_changed(); } void t_phone::cleanup_dead_lines(void) { t_rwmutex_writer x(lines_mtx); // Only remove idle lines at the end of the dead pool to avoid // moving lines in the vector. while (lines.size() > NUM_CALL_LINES && lines.back()->get_state() == LS_IDLE) { log_file->write_header("t_phone::cleanup_dead_lines", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Removed dead line "); log_file->write_raw(lines.back()->get_line_number() + 1); log_file->write_endl(); log_file->write_footer(); MEMMAN_DELETE(lines.back()); delete lines.back(); lines.pop_back(); } } void t_phone::move_releasing_lines_to_background(void) { t_rwmutex_writer x(lines_mtx); // NOTE: the line on the REFERRER position is not moved to the // background as a subscription may still be active. for (int i = 0; i < NUM_USER_LINES; i++) { if (lines.at(i)->get_substate() == LSSUB_RELEASING && !lines.at(i)->get_keep_seized()) { move_line_to_background(i); } } } void t_phone::cleanup_3way_state(unsigned short lineno) { assert(lineno < lines.size()); t_mutex_guard x(mutex_3way); // Clean up 3-way data if the line was involved in a 3-way if (is_3way) { bool line_in_3way = false; t_audio_session *as_peer; t_line *line_peer; if (lineno == line1_3way->get_line_number()) { line_in_3way = true; line_peer = line2_3way; } else if (lineno == line2_3way->get_line_number()) { line_in_3way = true; line_peer = line1_3way; } if (line_in_3way) { // Stop the 3-way mixing on the peer line as_peer = line_peer->get_audio_session(); if (as_peer) as_peer->stop_3way(); // Make the peer line the active line set_active_line(line_peer->get_line_number()); // If the 3-way was with mixed codec sample rates, then // the remaining audio session might have a mismatch // between the sound card sample rate and the codec // sample rate. In that case clear the sample rate by // toggling the audio session off and on. if (!as_peer->matching_sample_rates()) { log_file->write_report( "Hold/retrieve call to align codec and sound card.", "t_phone::line_cleared", LOG_NORMAL, LOG_DEBUG); line_peer->hold(true); line_peer->retrieve(); } is_3way = false; line1_3way = NULL; line2_3way = NULL; ui->cb_line_state_changed(); } } } void t_phone::cleanup_3way(void) { if (!is_3way) return; if (line1_3way->get_substate() == LSSUB_IDLE) { cleanup_3way_state(line1_3way->get_line_number()); } else if (line2_3way->get_substate() == LSSUB_IDLE) { cleanup_3way_state(line2_3way->get_line_number()); } } void t_phone::invite(t_phone_user *pu, const t_url &to_uri, const string &to_display, const string &subject, bool no_fork, bool anonymous) { t_rwmutex_reader x(lines_mtx); // Ignore if active line is not idle if (lines[active_line]->get_state() != LS_IDLE) { return; } lines[active_line]->invite(pu, to_uri, to_display, subject, no_fork, anonymous); } void t_phone::answer(void) { t_rwmutex_reader x(lines_mtx); // Ignore if active line is idle if (lines[active_line]->get_state() == LS_IDLE) return; lines[active_line]->answer(); } void t_phone::reject(void) { t_rwmutex_reader x(lines_mtx); // Ignore if active line is idle if (lines[active_line]->get_state() == LS_IDLE) return; lines[active_line]->reject(); } void t_phone::reject(unsigned short line) { t_rwmutex_reader x(lines_mtx); if (line > NUM_USER_LINES) return; if (lines[line]->get_state() == LS_IDLE) return; lines[line]->reject(); } void t_phone::redirect(const list &destinations, int code, string reason) { t_rwmutex_reader x(lines_mtx); // Ignore if active line is idle if (lines[active_line]->get_state() == LS_IDLE) return; lines[active_line]->redirect(destinations, code, reason); } void t_phone::end_call(void) { t_rwmutex_writer x(lines_mtx); // If 3-way is active then end call on both lines if (is_3way && ( active_line == line1_3way->get_line_number() || active_line == line2_3way->get_line_number())) { if (sys_config->get_hangup_both_3way()) { line1_3way->end_call(); line2_3way->end_call(); // NOTE: moving a line to the dying pool causes the // 3way line pointers to be cleared. unsigned short lineno1 = line1_3way->get_line_number(); unsigned short lineno2 = line2_3way->get_line_number(); move_line_to_background(lineno1); move_line_to_background(lineno2); } else { t_rwmutex_reader x(lines_mtx); // Hangup the active line, and make the next // line active. int l = active_line; activate_line((l+1) % NUM_USER_LINES); lines.at(l)->end_call(); move_line_to_background(l); } return; } // Ignore if active line is idle if (lines.at(active_line)->get_state() == LS_IDLE) return; lines.at(active_line)->end_call(); move_line_to_background(active_line); } void t_phone::registration(t_phone_user *pu, t_register_type register_type, unsigned long expires) { pu->registration(register_type, false, expires); } void t_phone::options(t_phone_user *pu, const t_url &to_uri, const string &to_display) { pu->options(to_uri, to_display); } void t_phone::options(void) { t_rwmutex_reader x(lines_mtx); lines[active_line]->options(); } bool t_phone::hold(bool rtponly) { // A line in a 3-way call cannot be held if (is_3way && ( active_line == line1_3way->get_line_number() || active_line == line2_3way->get_line_number())) { return false; } t_rwmutex_reader x(lines_mtx); return lines[active_line]->hold(rtponly); } void t_phone::retrieve(void) { t_rwmutex_reader x(lines_mtx); lines[active_line]->retrieve(); } void t_phone::refer(const t_url &uri, const string &display) { t_rwmutex_reader x(lines_mtx); lines[active_line]->refer(uri, display); } void t_phone::refer(unsigned short lineno_from, unsigned short lineno_to) { t_rwmutex_reader x(lines_mtx); // The nicest transfer is an attended transfer. An attended transfer // is only possible of the transfer target supports the 'replaces' // extension (RFC 3891). // If 'replaces' is not supported, then a transfer with consultation // is done. First hang up the consultation call, then transfer the // line. // HACK: if the call is in progress, then assume that Replaces is // supported. We don't know if it is as the call is not established // yet. An in-progress call can only be replaced if the user // deliberately allowed this (allow_transfer_consultation_inprog). if (lines.at(lineno_to)->remote_extension_supported(EXT_REPLACES)) { log_file->write_report("Remote end supports 'replaces'.\n"\ "Attended transfer.", "t_phone::refer"); refer_attended(lineno_from, lineno_to); } else if (get_line_substate(lineno_to) == LSSUB_OUTGOING_PROGRESS) { log_file->write_report("Call transfer while consultation in progress.\n"\ "Attended transfer.", "t_phone::refer"); refer_attended(lineno_from, lineno_to); } else { log_file->write_report("Remote end does not support 'replaces'.\n"\ "Transfer with consultation.", "t_phone::refer"); refer_consultation(lineno_from, lineno_to); } } // Attended call transfer // See draft-ietf-sipping-cc-transfer-07 7.3 void t_phone::refer_attended(unsigned short lineno_from, unsigned short lineno_to) { t_rwmutex_reader x(lines_mtx); t_line *line = lines.at(lineno_to); switch (line->get_substate()) { case LSSUB_ESTABLISHED: // Transfer allowed break; case LSSUB_OUTGOING_PROGRESS: { unsigned short dummy; t_user *user_config = get_line_user(lineno_to); if (!user_config->get_allow_transfer_consultation_inprog() || !is_line_transfer_consult(lineno_to, dummy)) { // Transfer not allowed return; } } // Transfer allowed break; default: // Transfer not allowed return; }; t_user *user_config = get_line_user(lineno_from); // draft-ietf-sipping-cc-transfer-07 section 7.3 // The call must be referred to the contact URI of the far-end. // As the contact URI may not be globally routable, the AoR // may be used alternatively. t_url uri; string display; if (user_config->get_attended_refer_to_aor()) { if (line->get_substate() == LSSUB_OUTGOING_PROGRESS) { uri = line->get_remote_uri_pending(); display = line->get_remote_target_display_pending(); } else { uri = line->get_remote_uri(); display = line->get_remote_target_display(); } } else { if (line->get_substate() == LSSUB_OUTGOING_PROGRESS) { uri = line->get_remote_target_uri_pending(); display = line->get_remote_target_display_pending(); } else { uri = line->get_remote_target_uri(); display = line->get_remote_target_display(); } } // Create Replaces header for replacing the call on lineno_to t_hdr_replaces hdr_replaces; if (line->get_substate() == LSSUB_OUTGOING_PROGRESS) { hdr_replaces.set_call_id(line->get_call_id_pending()); hdr_replaces.set_from_tag(line->get_local_tag_pending()); hdr_replaces.set_to_tag(line->get_remote_tag_pending()); } else { hdr_replaces.set_call_id(line->get_call_id()); hdr_replaces.set_from_tag(line->get_local_tag()); hdr_replaces.set_to_tag(line->get_remote_tag()); } uri.add_header(hdr_replaces); // draft-ietf-sipping-cc-transfer-07 section 7.3 // If the call is referred to the AoR, then add a Require header // that requires the 'Replaces' extension, to make the correct phone // ring in case of forking. if (user_config->get_attended_refer_to_aor()) { t_hdr_require hdr_require; hdr_require.add_feature(EXT_REPLACES); uri.add_header(hdr_require); } // Transfer call lines.at(lineno_from)->refer(uri, display); } // Call transfer with consultation // See draft-ietf-sipping-cc-transfer-07 7 void t_phone::refer_consultation(unsigned short lineno_from, unsigned short lineno_to) { t_rwmutex_writer x(lines_mtx); t_line *line = lines.at(lineno_to); if (line->get_substate() != LSSUB_ESTABLISHED) { return; } // Refer call to the URI of the far-end t_url uri = line->get_remote_uri(); string display = line->get_remote_display(); // End consultation call line->end_call(); move_line_to_background(lineno_to); // Transfer call lines.at(lineno_from)->refer(uri, display); } void t_phone::setup_consultation_call(const t_url &uri, const string &display) { unsigned short consult_line; if (!get_idle_line(consult_line)) { log_file->write_report("Cannot get idle line for consultation call.", "t_phone::setup_consultation_call"); return; } unsigned short xfer_line = active_line; t_user *user_config = get_line_user(xfer_line); t_phone_user *pu = find_phone_user(user_config->get_profile_name()); if (!pu) { log_file->write_header("t_phone::setup_consultation_call", LOG_NORMAL, LOG_WARNING); log_file->write_raw("User profile not active: "); log_file->write_raw(user_config->get_profile_name()); log_file->write_footer(); } activate_line(consult_line); string subject = TRANSLATE("Call transfer - %1"); subject = replace_first(subject, "%1", ui->format_sip_address(user_config, get_remote_display(xfer_line), get_remote_uri(xfer_line))); bool no_fork = false; if (user_config->get_allow_transfer_consultation_inprog()) { // If the configuration allows a call to be transferred // while the consultation call is still in progress, then // we send a no-fork request disposition in the INVTE // to setup the consultation call. This way we know that // the call has not been forked and it should be possible // to replace the early dialog. // // The scenario is: // A calls B // B sets up a consultation call to C (no-fork) // B sends a REFER with replaces to A // A sends an INVITE with replaces to C. // // NOTE: this is a non-standard implementation. RFC 3891 // does not allow to replace an early dialog not // setup by the UA. In this case the REFER from A to C // intends to replace the dialog from B to C, but C // did not setup the B-C dialog itself. no_fork = true; } invite(pu, uri, display, subject, no_fork, false); t_rwmutex_reader x(lines_mtx); lines.at(consult_line)->set_is_transfer_consult(true, xfer_line); lines.at(xfer_line)->set_to_be_transferred(true, consult_line); ui->cb_consultation_call_setup(user_config, consult_line); } void t_phone::activate_line(unsigned short l) { unsigned short a = get_active_line(); if (a == l) return; // Just switch the active line if there is a conference. if (is_3way) { set_active_line(l); ui->cb_line_state_changed(); return; } // Put the current active line on hold if it has a call. // Only established calls can be put on-hold. Transient calls // should be torn down or just kept in the same transient state // when switching to the other line. if (get_line(a)->get_state() == LS_BUSY && !hold()) { // The line is busy but could not be put on-hold. Determine // what to do based on the line sub state. switch(get_line(a)->get_substate()) { case LSSUB_OUTGOING_PROGRESS: // User has outgoing call in progress on the active // line, but decided to switch line, so tear down // the call. end_call(); ui->cb_stop_call_notification(a); break; case LSSUB_INCOMING_PROGRESS: // The incoming call on the current active will stay, // just stop the ring tone. ui->cb_stop_call_notification(a); break; case LSSUB_ANSWERING: // Answering is in progress, so call cannot be put // on-hold. Tear down the call. end_call(); break; case LSSUB_RELEASING: // The releasing call on the current line will get // released. No need to take any action here. break; default: // This should not happen. log_file->write_report("ERROR: Call cannot be put on hold.", "t_phone::activate_line"); } } set_active_line(l); t_rwmutex_reader x(lines_mtx); // Retrieve the call on the new active line unless that line // is transferring a call and the user profile indicates that // the referrer holds the call during call transfer. t_user *user_config = lines[l]->get_user(); if (get_line_refer_state(l) == REFST_NULL || (user_config && !user_config->get_referrer_hold())) { retrieve(); } // Play ring tone, if the new active line has an incoming call // in progress. if (get_line(l)->get_substate() == LSSUB_INCOMING_PROGRESS) { ui->cb_play_ringtone(l); } ui->cb_line_state_changed(); } void t_phone::send_dtmf(char digit, bool inband, bool info) { t_rwmutex_reader x(lines_mtx); lines[active_line]->send_dtmf(digit, inband, info); } void t_phone::start_timer(t_phone_timer timer, t_phone_user *pu) { t_tmr_phone *t; t_user *user_config = pu->get_user_profile(); switch(timer) { case PTMR_NAT_KEEPALIVE: t = new t_tmr_phone(user_config->get_timer_nat_keepalive() * 1000, timer, this); MEMMAN_NEW(t); pu->id_nat_keepalive = t->get_object_id(); break; case PTMR_TCP_PING: t = new t_tmr_phone(user_config->get_timer_tcp_ping() * 1000, timer, this); MEMMAN_NEW(t); pu->id_tcp_ping = t->get_object_id(); break; default: assert(false); } evq_timekeeper->push_start_timer(t); MEMMAN_DELETE(t); delete t; } void t_phone::stop_timer(t_phone_timer timer, t_phone_user *pu) { unsigned short *id; switch(timer) { case PTMR_REGISTRATION: id = &pu->id_registration; break; case PTMR_NAT_KEEPALIVE: id = &pu->id_nat_keepalive; break; case PTMR_TCP_PING: id = &pu->id_tcp_ping; break; default: assert(false); } if (*id != 0) evq_timekeeper->push_stop_timer(*id); *id = 0; } void t_phone::start_set_timer(t_phone_timer timer, long time, t_phone_user *pu) { t_tmr_phone *t; switch(timer) { case PTMR_REGISTRATION: long new_time; // Re-register before registration expires if (pu->get_last_reg_failed() || time <= RE_REGISTER_DELTA * 1000) { new_time = time; } else { new_time = time - (RE_REGISTER_DELTA * 1000); } t = new t_tmr_phone(new_time, timer, this); MEMMAN_NEW(t); pu->id_registration = t->get_object_id(); break; default: assert(false); } evq_timekeeper->push_start_timer(t); MEMMAN_DELETE(t); delete t; } void t_phone::handle_response_out_of_dialog(t_response *r, t_tuid tuid, t_tid tid) { t_phone_user *pu = match_phone_user(r, tuid); if (!pu) { log_file->write_report("Response does not match any pending request.", "t_phone::handle_response_out_of_dialog"); return; } log_file->write_header("t_phone::handle_response_out_of_dialog", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Out of dialog matches phone user: "); log_file->write_raw(pu->get_user_profile()->get_profile_name()); log_file->write_endl(); log_file->write_footer(); pu->handle_response_out_of_dialog(r, tuid, tid); } void t_phone::handle_response_out_of_dialog(StunMessage *r, t_tuid tuid) { t_phone_user *pu = match_phone_user(r, tuid); if (!pu) { log_file->write_report("STUN response does not match any pending request.", "t_phone::handle_response_out_of_dialog"); return; } pu->handle_response_out_of_dialog(r, tuid); } t_phone_user *t_phone::find_phone_user(const string &profile_name) const { for (list::const_iterator i = phone_users.begin(); i != phone_users.end(); ++i) { if (!(*i)->is_active()) continue; t_user *user_config = (*i)->get_user_profile(); if (user_config->get_profile_name() == profile_name) { return *i; } } return NULL; } t_phone_user *t_phone::find_phone_user(const t_url &user_uri) const { for (list::const_iterator i = phone_users.begin(); i != phone_users.end(); ++i) { if (!(*i)->is_active()) continue; t_user *user_config = (*i)->get_user_profile(); if (t_url(user_config->create_user_uri(false)) == user_uri) { return *i; } } return NULL; } t_phone_user *t_phone::match_phone_user(t_response *r, t_tuid tuid, bool active_only) { t_rwmutex_reader x(phone_users_mtx); for (list::iterator i = phone_users.begin(); i != phone_users.end(); ++i) { if (active_only && !(*i)->is_active()) continue; if ((*i)->match(r, tuid)) return *i; } return NULL; } t_phone_user *t_phone::match_phone_user(t_request *r, bool active_only) { t_rwmutex_reader x(phone_users_mtx); for (list::iterator i = phone_users.begin(); i != phone_users.end(); ++i) { if (active_only && !(*i)->is_active()) continue; if ((*i)->match(r)) return *i; } return NULL; } t_phone_user *t_phone::match_phone_user(StunMessage *r, t_tuid tuid, bool active_only) { t_rwmutex_reader x(phone_users_mtx); for (list::iterator i = phone_users.begin(); i != phone_users.end(); ++i) { if (active_only && !(*i)->is_active()) continue; if ((*i)->match(r, tuid)) return *i; } return NULL; } int t_phone::hunt_line(void) { t_rwmutex_reader x(lines_mtx); // Send incoming call to active line if it is idle. if (lines.at(active_line)->get_substate() == LSSUB_IDLE) { return active_line; } if (sys_config->get_call_waiting() || all_lines_idle()) { // Send the INVITE to the first idle unseized line for (unsigned short i = 0; i < NUM_USER_LINES; i++) { if (lines[i]->get_substate() == LSSUB_IDLE) { return i; } } } return -1; } ////////////// // Protected ////////////// void t_phone::recvd_provisional(t_response *r, t_tuid tuid, t_tid tid) { t_rwmutex_reader x(lines_mtx); for (unsigned short i = 0; i < lines.size(); i++) { if (lines[i]->match(r, tuid)) { lines[i]->recvd_provisional(r, tuid, tid); return; } } // out-of-dialog response // Provisional responses should only be given for INVITE. // A response for an INVITE is always in a dialog. // Ignore provisional responses for other requests. } void t_phone::recvd_success(t_response *r, t_tuid tuid, t_tid tid) { t_rwmutex_reader x(lines_mtx); for (unsigned short i = 0; i < lines.size(); i++) { if (lines[i]->match(r, tuid)) { lines[i]->recvd_success(r, tuid, tid); return; } } // out-of-dialog responses handle_response_out_of_dialog(r, tuid, tid); } void t_phone::recvd_redirect(t_response *r, t_tuid tuid, t_tid tid) { t_rwmutex_reader x(lines_mtx); for (unsigned short i = 0; i < lines.size(); i++) { if (lines[i]->match(r, tuid)) { lines[i]->recvd_redirect(r, tuid, tid); return; } } // out-of-dialog responses handle_response_out_of_dialog(r, tuid, tid); } void t_phone::recvd_client_error(t_response *r, t_tuid tuid, t_tid tid) { t_rwmutex_reader x(lines_mtx); for (unsigned short i = 0; i < lines.size(); i++) { if (lines[i]->match(r, tuid)) { lines[i]->recvd_client_error(r, tuid, tid); return; } } // out-of-dialog responses handle_response_out_of_dialog(r, tuid, tid); } void t_phone::recvd_server_error(t_response *r, t_tuid tuid, t_tid tid) { t_rwmutex_reader x(lines_mtx); for (unsigned short i = 0; i < lines.size(); i++) { if (lines[i]->match(r, tuid)) { lines[i]->recvd_server_error(r, tuid, tid); return; } } // out-of-dialog responses handle_response_out_of_dialog(r, tuid, tid); } void t_phone::recvd_global_error(t_response *r, t_tuid tuid, t_tid tid) { t_rwmutex_reader x(lines_mtx); for (unsigned short i = 0; i < lines.size(); i++) { if (lines[i]->match(r, tuid)) { lines[i]->recvd_global_error(r, tuid, tid); return; } } // out-of-dialog responses handle_response_out_of_dialog(r, tuid, tid); } void t_phone::post_process_response(t_response *r, t_tuid tuid, t_tid tid) { cleanup_dead_lines(); move_releasing_lines_to_background(); cleanup_3way(); } void t_phone::recvd_invite(t_request *r, t_tid tid) { t_rwmutex_reader x(lines_mtx); // Check if this INVITE is a retransmission. // Once the TU sent a 2XX repsonse on an INVITE it has to deal // with retransmissions. for (unsigned short i = 0; i < lines.size(); i++) { if (lines[i]->is_invite_retrans(r)) { lines[i]->process_invite_retrans(); return; } } // RFC 3261 12.2.2 // An INVITE with a To-header without a tag is an initial // INVITE if (r->hdr_to.tag == "") { recvd_initial_invite(r, tid); } else { recvd_re_invite(r, tid); } } void t_phone::recvd_initial_invite(t_request *r, t_tid tid) { t_response *resp; list unsupported; t_call_record call_record; // Find out for which user this INVITE is. t_phone_user *pu = match_phone_user(r, true); if (!pu) { resp = r->create_response(R_404_NOT_FOUND); send_response(resp, 0, tid); // Do not create a call history record as this is a misrouted // call. MEMMAN_DELETE(resp); delete resp; return; } // Reject call if phone is not active if (!is_active) { resp = r->create_response(R_480_TEMP_NOT_AVAILABLE); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return; } t_user *user_config = pu->get_user_profile(); // Check if the far end requires any unsupported extensions if (!user_config->check_required_ext(r, unsupported)) { // Not all required extensions are supported resp = r->create_response(R_420_BAD_EXTENSION); resp->hdr_unsupported.set_features(unsupported); send_response(resp, 0, tid); // Do not create a call history record here. The far-end // should retry the call without the extension, so this // is not a missed call from the user point of view. MEMMAN_DELETE(resp); delete resp; return; } // RFC 3891 3 // If a replaces header is present, check if it matches a dialog int replace_line = -1; if (r->hdr_replaces.is_populated() && user_config->get_ext_replaces()) { bool early_matched = false; bool no_fork_req_disposition = r->hdr_request_disposition.is_populated() && r->hdr_request_disposition.fork_directive == t_hdr_request_disposition::NO_FORK; t_rwmutex_reader x(lines_mtx); for (size_t i = 0; i < lines.size(); i++) { if (lines.at(i)->match_replaces(r->hdr_replaces.call_id, r->hdr_replaces.to_tag, r->hdr_replaces.from_tag, no_fork_req_disposition, early_matched)) { replace_line = i; break; } } if (replace_line >= NUM_CALL_LINES) { // Replaces header matches a releasing line. resp = r->create_response(R_603_DECLINE); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return; } else if (replace_line >= 0) { if (replace_line == active_line) { if (r->hdr_replaces.early_only && !early_matched) { resp = r->create_response(R_486_BUSY_HERE); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return; } // The existing call will be torn down only after // it has been checked that this incoming INVITE // is not rejected by the user, e.g. DND. } else { // Implementation decision: // Don't allow a held call to be replaced. resp = r->create_response(R_486_BUSY_HERE); send_response(resp, 0, tid); // Create a call history record call_record.start_call(r, t_call_record::DIR_IN, user_config->get_profile_name()); call_record.fail_call(resp); call_history->add_call_record(call_record); MEMMAN_DELETE(resp); delete resp; return; } } else { // Replaces does not match any line. resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return; } } // Hunt for an idle line to handle the call. int hunted_line = -1; if (replace_line >= 0) { hunted_line = replace_line; } else { hunted_line = hunt_line(); } t_display_url display_url; list cf_dest; // call forwarding destinations // Call user defineable incoming call script to determine how // to handle this call t_script_result script_result; if (!user_config->get_script_incoming_call().empty()) { // Send 100 Trying as the script might take a while resp = r->create_response(R_100_TRYING); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; t_call_script script(user_config, t_call_script::TRIGGER_IN_CALL, hunted_line + 1); script.exec_action(script_result, r); if (!script_result.display_msgs.empty()) { string text(join_strings(script_result.display_msgs, "\n")); ui->cb_display_msg(text, MSG_NO_PRIO); } // Override display name with caller name returned by script if (!script_result.caller_name.empty()) { r->hdr_from.display_override = script_result.caller_name; log_file->write_header("t_phone::recvd_invite", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Override display name with caller name:\n"); log_file->write_raw(script_result.caller_name); log_file->write_endl(); log_file->write_footer(); } } t_call_script script_in_call_failed(user_config, t_call_script::TRIGGER_IN_CALL_FAILED, 0); // Lookup address in address book. if (script_result.caller_name.empty() && sys_config->get_ab_lookup_name() && (sys_config->get_ab_override_display() || r->hdr_from.display.empty())) { // Send 100 Trying as name lookup might take a while resp = r->create_response(R_100_TRYING); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; string name = ui->get_name_from_abook(user_config, r->hdr_from.uri); if (!name.empty()) { r->hdr_from.display_override = name; log_file->write_header("t_phone::recvd_invite", LOG_NORMAL, LOG_DEBUG); log_file->write_raw( "Override display name with address book name:\n"); log_file->write_raw(name); log_file->write_endl(); log_file->write_footer(); } } // Perform the action in the script_result. // NOTE: the default action is "continue" switch (script_result.action) { case t_script_result::ACTION_CONTINUE: // Continue with call break; case t_script_result::ACTION_AUTOANSWER: log_file->write_report("Incoming call script action: autoanswer", "t_phone::recvd_invite"); break; case t_script_result::ACTION_REJECT: log_file->write_report("Incoming call script action: reject", "t_phone::recvd_invite"); resp = r->create_response(R_603_DECLINE, script_result.reason); send_response(resp, 0, tid); // Create a call history record call_record.start_call(r, t_call_record::DIR_IN, user_config->get_profile_name()); call_record.fail_call(resp); call_history->add_call_record(call_record); // Trigger call script script_in_call_failed.exec_notify(resp); MEMMAN_DELETE(resp); delete resp; return; break; case t_script_result::ACTION_DND: log_file->write_report("Incoming call script action: dnd", "t_phone::recvd_invite"); resp = r->create_response(R_480_TEMP_NOT_AVAILABLE, script_result.reason); send_response(resp, 0, tid); // Create a call history record call_record.start_call(r, t_call_record::DIR_IN, user_config->get_profile_name()); call_record.fail_call(resp); call_history->add_call_record(call_record); // Trigger call script script_in_call_failed.exec_notify(resp); MEMMAN_DELETE(resp); delete resp; return; break; case t_script_result::ACTION_REDIRECT: log_file->write_report("Incoming call script action: redirect", "t_phone::recvd_invite"); ui->expand_destination(user_config, script_result.contact, display_url); if (display_url.is_valid()) { cf_dest.clear(); cf_dest.push_back(display_url); resp = r->create_response(R_302_MOVED_TEMPORARILY); resp->hdr_contact.set_contacts(cf_dest); } else { log_file->write_report("Invalid redirect contact", "t_phone::recvd_invite", LOG_NORMAL, LOG_WARNING); resp = r->create_response(R_500_INTERNAL_SERVER_ERROR); } send_response(resp, 0, tid); // Create a call history record call_record.start_call(r, t_call_record::DIR_IN, user_config->get_profile_name()); call_record.fail_call(resp); call_history->add_call_record(call_record); // Trigger call script script_in_call_failed.exec_notify(resp); MEMMAN_DELETE(resp); delete resp; return; break; default: log_file->write_report("Error in incoming call script", "t_phone::recvd_invite", LOG_NORMAL, LOG_WARNING); resp = r->create_response(R_500_INTERNAL_SERVER_ERROR); send_response(resp, 0, tid); // Create a call history record call_record.start_call(r, t_call_record::DIR_IN, user_config->get_profile_name()); call_record.fail_call(resp); call_history->add_call_record(call_record); // Trigger call script script_in_call_failed.exec_notify(resp); MEMMAN_DELETE(resp); delete resp; return; break; } // Call forwarding always // NOTE: if a call script returned the autoanswer action, then // call forwarding should be bypassed if (pu->service->get_cf_active(CF_ALWAYS, cf_dest) && script_result.action == t_script_result::ACTION_CONTINUE) { log_file->write_report("Call redirection unconditional", "t_phone::recvd_invite"); resp = r->create_response(R_302_MOVED_TEMPORARILY); resp->hdr_contact.set_contacts(cf_dest); send_response(resp, 0, tid); // Create a call history record call_record.start_call(r, t_call_record::DIR_IN, user_config->get_profile_name()); call_record.fail_call(resp); call_history->add_call_record(call_record); // Trigger call script script_in_call_failed.exec_notify(resp); MEMMAN_DELETE(resp); delete resp; return; } // Do not disturb // RFC 3261 21.4.18 // NOTE: if a call script returned the autoanswer action, then // do not disturb should be bypassed if (pu->service->is_dnd_active() && script_result.action == t_script_result::ACTION_CONTINUE) { log_file->write_report("Do not disturb", "t_phone::recvd_invite"); resp = r->create_response(R_480_TEMP_NOT_AVAILABLE); send_response(resp, 0, tid); // Create a call history record call_record.start_call(r, t_call_record::DIR_IN, user_config->get_profile_name()); call_record.fail_call(resp); call_history->add_call_record(call_record); // Trigger call script script_in_call_failed.exec_notify(resp); MEMMAN_DELETE(resp); delete resp; return; } // RFC 3891 bool auto_answer_replace_call = false; if (replace_line >= 0) { // This call replaces an existing call. Tear down this existing // call. This will clear the active line. log_file->write_report("End call due to Replaces header.", "t_phone::recvd_initial_invite"); t_rwmutex_writer x(lines_mtx); if (lines.at(replace_line)->get_substate() == LSSUB_INCOMING_PROGRESS) { ui->cb_stop_call_notification(replace_line); lines.at(replace_line)->reject(); } else { lines.at(replace_line)->end_call(); auto_answer_replace_call = true; } move_line_to_background(replace_line); } // Auto answer if (hunted_line == active_line) { // Auto-answer is only applicable to the active line. t_rwmutex_reader x(lines_mtx); if (replace_line >= 0 && auto_answer_replace_call) { // RFC 3891 // This call replaces an existing established call, answer immediate. lines.at(active_line)->set_auto_answer(true); } else if (pu->service->is_auto_answer_active() || script_result.action == t_script_result::ACTION_AUTOANSWER) { // Auto answer log_file->write_report("Auto answer", "t_phone::recvd_invite"); lines.at(active_line)->set_auto_answer(true); } } t_rwmutex_reader x(lines_mtx); // Send INVITE to hunted line if (hunted_line >= 0) { lines.at(hunted_line)->recvd_invite(pu, r, tid, script_result.ringtone); return; } // The phone is busy // Call forwarding busy if (pu->service->get_cf_active(CF_BUSY, cf_dest)) { log_file->write_report("Call redirection busy", "t_phone::recvd_invite"); resp = r->create_response(R_302_MOVED_TEMPORARILY); resp->hdr_contact.set_contacts(cf_dest); send_response(resp, 0, tid); // Create a call history record call_record.start_call(r, t_call_record::DIR_IN, user_config->get_profile_name()); call_record.fail_call(resp); call_history->add_call_record(call_record); // Trigger call script script_in_call_failed.exec_notify(resp); MEMMAN_DELETE(resp); delete resp; return; } // Send busy response resp = r->create_response(R_486_BUSY_HERE); send_response(resp, 0, tid); // Create a call history record call_record.start_call(r, t_call_record::DIR_IN, user_config->get_profile_name()); call_record.fail_call(resp); call_history->add_call_record(call_record); // Trigger call script script_in_call_failed.exec_notify(resp); MEMMAN_DELETE(resp); delete resp; } void t_phone::recvd_re_invite(t_request *r, t_tid tid) { t_response *resp; list unsupported; t_rwmutex_reader x(lines_mtx); // RFC 3261 12.2.2 // A To-header with a tag is a mid-dialog request. // Find a line that matches the request for (unsigned short i = 0; i < lines.size(); i++) { if (lines[i]->match(r)) { t_phone_user *pu = lines[i]->get_phone_user(); assert(pu); t_user *user_config = pu->get_user_profile(); // Check if the far end requires any unsupported extensions if (!user_config->check_required_ext(r, unsupported)) { // Not all required extensions are supported resp = r->create_response(R_420_BAD_EXTENSION); resp->hdr_unsupported.set_features(unsupported); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return; } lines[i]->recvd_invite(pu, r, tid, ""); return; } } // No dialog matches with the request. resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; } void t_phone::recvd_ack(t_request *r, t_tid tid) { t_response *resp; t_rwmutex_reader x(lines_mtx); for (unsigned short i = 0; i < lines.size(); i++) { if (lines[i]->match(r)) { lines[i]->recvd_ack(r, tid); return; } } resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; } void t_phone::recvd_cancel(t_request *r, t_tid cancel_tid, t_tid target_tid) { t_response *resp; t_rwmutex_reader x(lines_mtx); for (unsigned short i = 0; i < lines.size(); i++) { if (lines[i]->match_cancel(r, target_tid)) { lines[i]->recvd_cancel(r, cancel_tid, target_tid); return; } } resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); send_response(resp, 0, cancel_tid); MEMMAN_DELETE(resp); delete resp; } void t_phone::recvd_bye(t_request *r, t_tid tid) { t_response *resp; list unsupported; t_rwmutex_reader x(lines_mtx); for (unsigned short i = 0; i < lines.size(); i++) { if (lines[i]->match(r)) { t_user *user_config = lines[i]->get_user(); assert(user_config); if (!user_config->check_required_ext(r, unsupported)) { // Not all required extensions are supported resp = r->create_response(R_420_BAD_EXTENSION); resp->hdr_unsupported.set_features(unsupported); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return; } lines[i]->recvd_bye(r, tid); return; } } resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; } void t_phone::recvd_options(t_request *r, t_tid tid) { t_response *resp; if (r->hdr_to.tag =="") { // Out-of-dialog OPTIONS t_phone_user *pu = find_phone_user_out_dialog_request(r, tid); if (pu) { resp = pu->create_options_response(r); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; } } else { // In-dialog OPTIONS t_line *l = find_line_in_dialog_request(r, tid); if (l) { l->recvd_options(r, tid); } } } t_phone_user *t_phone::find_phone_user_out_dialog_request(t_request *r, t_tid tid) { t_response *resp; list unsupported; // Find out for which user this request is. t_phone_user *pu = match_phone_user(r, true); if (!pu) { resp = r->create_response(R_404_NOT_FOUND); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return NULL; } // Check if the far end requires any unsupported extensions if (!pu->get_user_profile()->check_required_ext(r, unsupported)) { // Not all required extensions are supported resp = r->create_response(R_420_BAD_EXTENSION); resp->hdr_unsupported.set_features(unsupported); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return NULL; } return pu; } t_line *t_phone::find_line_in_dialog_request(t_request *r, t_tid tid) { t_response *resp; list unsupported; t_rwmutex_reader x(lines_mtx); // RFC 3261 12.2.2 // A To-header with a tag is a mid-dialog request. // No dialog matches with the request. for (unsigned short i = 0; i < lines.size(); i++) { if (lines[i]->match(r)) { t_user *user_config = lines[i]->get_user(); assert(user_config); // Check if the far end requires any unsupported extensions if (!user_config->check_required_ext(r, unsupported)) { // Not all required extensions are supported resp = r->create_response(R_420_BAD_EXTENSION); resp->hdr_unsupported.set_features(unsupported); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return NULL; } return lines[i]; } } resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return NULL; } void t_phone::recvd_register(t_request *r, t_tid tid) { // The softphone is not a registrar. t_response *resp = r->create_response(R_403_FORBIDDEN); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; // TEST ONLY: code for testing a 423 Interval Too Brief /* if (r->hdr_contact.contact_list.front().get_expires() < 30) { t_response *resp = r->create_response( R_423_INTERVAL_TOO_BRIEF); resp->hdr_min_expires.set_time(30); send_response(resp, 0, tid); delete resp; return; } // Code for testing a 200 OK response (register) t_response *resp = r->create_response(R_200_OK); resp->hdr_contact.set_contacts(r->hdr_contact.contact_list); resp->hdr_contact.contact_list.front().set_expires(30); resp->hdr_date.set_now(); send_response(resp, 0, tid); delete resp; // Code for testing 200 OK response (de-register) t_response *resp = r->create_response(R_200_OK); send_response(resp, 0, tid); delete resp; // Code for testing 200 OK response (query) t_response *resp = r->create_response(R_200_OK); t_contact_param contact; contact.uri.set_url("sip:aap@xs4all.nl"); resp->hdr_contact.add_contact(contact); contact.uri.set_url("sip:noot@xs4all.nl"); resp->hdr_contact.add_contact(contact); send_response(resp, 0, tid); delete resp; // Code for testing a 401 response (register) if (r->hdr_authorization.is_populated() && r->hdr_authorization.credentials_list.front().digest_response. nonce == "0123456789abcdef") { t_response *resp = r->create_response(R_200_OK); resp->hdr_contact.set_contacts(r->hdr_contact.contact_list); resp->hdr_contact.contact_list.front().set_expires(30); resp->hdr_date.set_now(); send_response(resp, 0, tid); delete resp; } else { t_response *resp = r->create_response(R_401_UNAUTHORIZED); t_challenge c; c.auth_scheme = AUTH_DIGEST; c.digest_challenge.realm = "mtel.nl"; if (r->hdr_authorization.is_populated()) { c.digest_challenge.nonce = "0123456789abcdef"; c.digest_challenge.stale = true; } else { c.digest_challenge.nonce = "aaaaaa0123456789"; } c.digest_challenge.opaque = "secret"; c.digest_challenge.algorithm = ALG_MD5; // c.digest_challenge.qop_options.push_back(QOP_AUTH); // c.digest_challenge.qop_options.push_back(QOP_AUTH_INT); resp->hdr_www_authenticate.set_challenge(c); send_response(resp, 0, tid); } */ } void t_phone::recvd_prack(t_request *r, t_tid tid) { t_response *resp; t_rwmutex_reader x(lines_mtx); for (unsigned short i = 0; i < lines.size(); i++) { if (lines[i]->match(r)) { lines[i]->recvd_prack(r, tid); return; } } resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; } void t_phone::recvd_subscribe(t_request *r, t_tid tid) { t_response *resp; if (r->hdr_event.event_type != SIP_EVENT_REFER) { // Non-supported event type resp = r->create_response(R_489_BAD_EVENT); resp->hdr_allow_events.add_event_type(SIP_EVENT_REFER); send_response(resp, 0 ,tid); MEMMAN_DELETE(resp); delete resp; return; } t_rwmutex_reader x(lines_mtx); for (unsigned short i = 0; i < lines.size(); i++) { if (lines[i]->match(r)) { lines[i]->recvd_subscribe(r, tid); return; } } if (r->hdr_to.tag == "") { // A REFER outside a dialog is not allowed by Twinkle if (r->hdr_event.event_type == SIP_EVENT_REFER) { // RFC 3515 2.4.4 resp = r->create_response(R_403_FORBIDDEN); send_response(resp, 0 ,tid); MEMMAN_DELETE(resp); delete resp; return; } } resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; } void t_phone::recvd_notify(t_request *r, t_tid tid) { t_response *resp; t_phone_user *pu; // Check support for the notified event if (!SIP_EVENT_SUPPORTED(r->hdr_event.event_type)) { // Non-supported event type resp = r->create_response(R_489_BAD_EVENT); ADD_SUPPORTED_SIP_EVENTS(resp->hdr_allow_events); send_response(resp, 0 ,tid); MEMMAN_DELETE(resp); delete resp; return; } // MWI or presence notification if (r->hdr_event.event_type == SIP_EVENT_MSG_SUMMARY || r->hdr_event.event_type == SIP_EVENT_PRESENCE) { pu = match_phone_user(r, true); if (pu) { pu->recvd_notify(r, tid); } else { resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); send_response(resp, 0 ,tid); MEMMAN_DELETE(resp); delete resp; } return; } // REFER notification t_rwmutex_reader x(lines_mtx); for (unsigned short i = 0; i < lines.size(); i++) { if (lines[i]->match(r)) { lines[i]->recvd_notify(r, tid); if (lines[i]->get_refer_state() == REFST_NULL) { // Refer subscription has finished. log_file->write_report("Refer subscription terminated.", "t_phone::recvd_notify"); if (lines[i]->get_substate() == LSSUB_RELEASING || lines[i]->get_substate() == LSSUB_IDLE) { // The line is (being) cleared already. So this // NOTIFY signals the end of the refer subscription // attached to this line. cleanup_dead_lines(); return; } else if (lines[i]->is_refer_succeeded()) { log_file->write_report( "Refer succeeded. End call with referee,", "t_phone::recvd_notify"); lines[i]->end_call(); } else { log_file->write_report("Refer failed.", "t_phone::recvd_notify"); t_user *user_config = lines[i]->get_user(); assert(user_config); if (user_config->get_referrer_hold() && lines[i]->get_is_on_hold()) { // Retrieve the call if the line is active. if (i == active_line) { log_file->write_report( "Retrieve call with referee.", "t_phone::recvd_notify"); lines[i]->retrieve(); } } } } return; } } if (r->hdr_to.tag == "") { // NOTIFY outside a dialog is not allowed. resp = r->create_response(R_403_FORBIDDEN); send_response(resp, 0 ,tid); MEMMAN_DELETE(resp); delete resp; return; } resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; } void t_phone::recvd_refer(t_request *r, t_tid tid) { t_response *resp; t_rwmutex_reader x(lines_mtx); for (unsigned short i = 0; i < lines.size(); i++) { if (lines[i]->match(r)) { t_phone_user *pu = lines[i]->get_phone_user(); assert(pu); t_user *user_config = pu->get_user_profile(); list unsupported; if (!user_config->check_required_ext(r, unsupported)) { // Not all required extensions are supported resp = r->create_response(R_420_BAD_EXTENSION); resp->hdr_unsupported.set_features(unsupported); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return; } // Reject if a 3-way call is established. if (is_3way) { log_file->write_report("3-way call active. Reject REFER.", "t_phone::recvd_refer"); resp = r->create_response(R_603_DECLINE); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return; } // Reject if the line is on-hold. if (is_3way || lines[i]->get_is_on_hold()) { log_file->write_report("Line is on-hold. Reject REFER.", "t_phone::recvd_refer"); resp = r->create_response(R_603_DECLINE); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return; } // Check if a refer is already in progress if (i == LINENO_REFERRER || lines[LINENO_REFERRER]->get_state() != LS_IDLE || incoming_refer_data != NULL) { log_file->write_report( "A REFER is still in progress. Reject REFER.", "t_phone::recvd_refer"); resp = r->create_response(R_603_DECLINE); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return; } if (!lines[i]->recvd_refer(r, tid)) { // Refer has been rejected. log_file->write_report("Incoming REFER rejected.", "t_phone::recvd_refer"); return; } // Make sure the line stays seized if the far-end ends the // call, so a line will be available if the user gives permission // for the call transfer. lines[i]->set_keep_seized(true); incoming_refer_data = new t_transfer_data(r, i, lines[i]->get_hide_user(), pu); MEMMAN_NEW(incoming_refer_data); return; } } if (r->hdr_to.tag == "") { // Twinkle does not allow a REFER outside a dialog. resp = r->create_response(R_403_FORBIDDEN); send_response(resp, 0 ,tid); MEMMAN_DELETE(resp); delete resp; return; } resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; } void t_phone::recvd_refer_permission(bool permission) { if (!incoming_refer_data) { // This should not happen log_file->write_report("Incoming REFER data is gone.", "t_phone::recvd_refer_permission", LOG_NORMAL, LOG_WARNING); return; } unsigned short i = incoming_refer_data->get_lineno(); t_request *r = incoming_refer_data->get_refer_request(); bool hide_user = incoming_refer_data->get_hide_user(); t_phone_user *pu = incoming_refer_data->get_phone_user(); t_user *user_config = pu->get_user_profile(); t_rwmutex_reader x(lines_mtx); lines[i]->recvd_refer_permission(permission, r); if (!permission) { log_file->write_report("Incoming REFER rejected.", "t_phone::recvd_refer_permission"); lines[i]->set_keep_seized(false); move_releasing_lines_to_background(); MEMMAN_DELETE(incoming_refer_data); delete incoming_refer_data; incoming_refer_data = NULL; return; } else { log_file->write_report("Incoming REFER allowed.", "t_phone::recvd_refer_permission"); } if (lines[i]->get_substate() == LSSUB_ESTABLISHED) { // Put line on-hold and place it in the referrer line log_file->write_report( "Hold call before calling the refer-target.", "t_phone::recvd_refer_permission"); if (user_config->get_referee_hold()) { lines[i]->hold(); } else { // The user profile indicates that the line should // not be put on-hold, i.e. do not send re-INVITE. // So only stop RTP. lines[i]->hold(true); } } // Move the original line to the REFERRER line (the line may be idle // already). t_line *l = lines[i]; lines[i] = lines[LINENO_REFERRER]; lines[i]->line_number = i; lines[LINENO_REFERRER] = l; lines[LINENO_REFERRER]->line_number = LINENO_REFERRER; lines[LINENO_REFERRER]->set_keep_seized(false); ui->cb_line_state_changed(); // Setup call to the Refer-To destination log_file->write_report("Call refer-target.", "t_phone::recvd_refer_permission"); t_hdr_replaces hdr_replaces; t_hdr_require hdr_require; // Analyze headers in Refer-To URI. // For an attended call transfer the Refer-To URI // will contain a Replaces header and possibly a Require // header. Other headers are ignored for now. // See draft-ietf-sipping-cc-transfer-07 7.3 if (!r->hdr_refer_to.uri.get_headers().empty()) { try { list parse_errors; t_sip_message *m = t_parser::parse_headers( r->hdr_refer_to.uri.get_headers(), parse_errors); hdr_replaces = m->hdr_replaces; hdr_require = m->hdr_require; MEMMAN_DELETE(m); delete m; } catch (int) { log_file->write_header("t_phone::recvd_refer_permission", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Cannot parse headers in Refer-To URI\n"); log_file->write_raw(r->hdr_refer_to.uri.encode()); log_file->write_endl(); log_file->write_footer(); } } ui->cb_call_referred(user_config, i, r); lines[i]->invite(pu, r->hdr_refer_to.uri.copy_without_headers(), r->hdr_refer_to.display, "", r->hdr_referred_by, hdr_replaces, hdr_require, t_hdr_request_disposition(), hide_user); lines[i]->open_dialog->is_referred_call = true; MEMMAN_DELETE(incoming_refer_data); delete incoming_refer_data; incoming_refer_data = NULL; return; } void t_phone::recvd_info(t_request *r, t_tid tid) { t_response *resp; list unsupported; t_rwmutex_reader x(lines_mtx); for (unsigned short i = 0; i < lines.size(); i++) { if (lines[i]->match(r)) { t_user *user_config = lines[i]->get_user(); assert(user_config); if (!user_config->check_required_ext(r, unsupported)) { // Not all required extensions are supported resp = r->create_response(R_420_BAD_EXTENSION); resp->hdr_unsupported.set_features(unsupported); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return; } lines[i]->recvd_info(r, tid); return; } } resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; } void t_phone::recvd_message(t_request *r, t_tid tid) { if (r->hdr_to.tag =="") { // Out-of-dialog MESSAGE t_phone_user *pu = find_phone_user_out_dialog_request(r, tid); if (pu) { pu->recvd_message(r, tid); } } else { // In-dialog MESSAGE t_line *l = find_line_in_dialog_request(r, tid); if (l) { l->recvd_message(r, tid); } } } void t_phone::post_process_request(t_request *r, t_tid cancel_tid, t_tid target_tid) { cleanup_dead_lines(); move_releasing_lines_to_background(); cleanup_3way(); } void t_phone::failure(t_failure failure, t_tid tid) { // TODO } void t_phone::recvd_stun_resp(StunMessage *r, t_tuid tuid, t_tid tid) { t_rwmutex_reader x(lines_mtx); for (unsigned short i = 0; i < lines.size(); i++) { if (lines[i]->match(r, tuid)) { lines[i]->recvd_stun_resp(r, tuid, tid); return; } } // out-of-dialog STUN responses handle_response_out_of_dialog(r, tuid); } void t_phone::handle_event_timeout(t_event_timeout *e) { t_timer *t = e->get_timer(); t_tmr_phone *tmr_phone; t_tmr_line *tmr_line; t_tmr_subscribe *tmr_subscribe; t_tmr_publish *tmr_publish; t_object_id line_id; switch (t->get_type()) { case TMR_PHONE: tmr_phone = dynamic_cast(t); timeout(tmr_phone->get_phone_timer(), tmr_phone->get_object_id()); break; case TMR_LINE: tmr_line = dynamic_cast(t); line_timeout(tmr_line->get_line_id(), tmr_line->get_line_timer(), tmr_line->get_dialog_id()); break; case TMR_SUBSCRIBE: tmr_subscribe = dynamic_cast(t); line_id = tmr_subscribe->get_line_id(); if (line_id == 0) { subscription_timeout(tmr_subscribe->get_subscribe_timer(), tmr_subscribe->get_object_id()); } else { line_timeout_sub(line_id, tmr_subscribe->get_subscribe_timer(), tmr_subscribe->get_dialog_id(), tmr_subscribe->get_sub_event_type(), tmr_subscribe->get_sub_event_id()); } break; case TMR_PUBLISH: tmr_publish = dynamic_cast(t); publication_timeout(tmr_publish->get_publish_timer(), tmr_publish->get_object_id()); break; default: assert(false); break; } } void t_phone::line_timeout(t_object_id id, t_line_timer timer, t_object_id did) { // If there is no line with id anymore, then the timer expires // silently. t_line *line = get_line_by_id(id); if (line) { line->timeout(timer, did); } } void t_phone::line_timeout_sub(t_object_id id, t_subscribe_timer timer, t_object_id did, const string &event_type, const string &event_id) { // If there is no line with id anymore, then the timer expires // silently. t_line *line = get_line_by_id(id); if (line) { line->timeout_sub(timer, did, event_type, event_id); } } void t_phone::subscription_timeout(t_subscribe_timer timer, t_object_id id_timer) { t_rwmutex_reader x(phone_users_mtx); for (list::iterator i = phone_users.begin(); i != phone_users.end(); i++) { if ((*i)->match_subscribe_timer(timer, id_timer)) { (*i)->timeout_sub(timer, id_timer); } } } void t_phone::publication_timeout(t_publish_timer timer, t_object_id id_timer) { t_rwmutex_reader x(phone_users_mtx); for (list::iterator i = phone_users.begin(); i != phone_users.end(); i++) { if ((*i)->match_publish_timer(timer, id_timer)) { (*i)->timeout_publish(timer, id_timer); } } } void t_phone::timeout(t_phone_timer timer, unsigned short id_timer) { t_rwmutex_reader x(phone_users_mtx); switch (timer) { case PTMR_REGISTRATION: for (list::iterator i = phone_users.begin(); i != phone_users.end(); ++i) { if ((*i)->id_registration == id_timer) { (*i)->timeout(timer); } } break; case PTMR_NAT_KEEPALIVE: for (list::iterator i = phone_users.begin(); i != phone_users.end(); ++i) { if ((*i)->id_nat_keepalive == id_timer) { (*i)->timeout(timer); } } break; case PTMR_TCP_PING: for (list::iterator i = phone_users.begin(); i != phone_users.end(); ++i) { if ((*i)->id_tcp_ping == id_timer) { (*i)->timeout(timer); } } break; default: assert(false); } } void t_phone::handle_broken_connection(t_event_broken_connection *e) { t_rwmutex_reader x(phone_users_mtx); // Find the phone user that was associated with the connection. // This phone user has to handle the event. t_phone_user *pu = find_phone_user(e->get_user_uri()); if (pu) { pu->handle_broken_connection(); } else { log_file->write_header("t_phone::handle_broken_connection", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Cannot find active phone user "); log_file->write_raw(e->get_user_uri().encode()); log_file->write_endl(); log_file->write_footer(); } } /////////// // Public /////////// t_phone::t_phone() : t_transaction_layer(), lines(NUM_CALL_LINES) { is_active = true; active_line = 0; // Create phone lines for (unsigned short i = 0; i < NUM_CALL_LINES; i++) { lines[i] = new t_line(this, i); MEMMAN_NEW(lines[i]); } // Initialize 3-way conference data is_3way = false; line1_3way = NULL; line2_3way = NULL; incoming_refer_data = NULL; struct timeval t; gettimeofday(&t, NULL); startup_time = t.tv_sec; // NOTE: The RTP ports for the lines are initialized after the // system settings have been read. } t_phone::~t_phone() { // Delete phone lines log_file->write_header("t_phone::~t_phone"); log_file->write_raw("Number of lines to cleanup: "); log_file->write_raw(lines.size()); log_file->write_endl(); log_file->write_footer(); if (incoming_refer_data) { MEMMAN_DELETE(incoming_refer_data); delete incoming_refer_data; } for (unsigned short i = 0; i < lines.size(); i++) { MEMMAN_DELETE(lines[i]); delete lines[i]; } t_rwmutex_writer x(phone_users_mtx); // Delete all phone users for (list::iterator i = phone_users.begin(); i != phone_users.end(); i++) { MEMMAN_DELETE(*i); delete *i; } } void t_phone::pub_invite(t_user *user, const t_url &to_uri, const string &to_display, const string &subject, bool anonymous) { t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) { invite(pu, to_uri, to_display, subject, false, anonymous); } else { log_file->write_header("t_phone::pub_invite", LOG_NORMAL, LOG_WARNING); log_file->write_raw("User profile not active: "); log_file->write_raw(user->get_profile_name()); log_file->write_footer(); } } void t_phone::pub_answer(void) { lock(); answer(); unlock(); } void t_phone::pub_reject(void) { lock(); reject(); unlock(); } void t_phone::pub_reject(unsigned short line) { lock(); reject(line); unlock(); } void t_phone::pub_redirect(const list &destinations, int code, string reason) { lock(); redirect(destinations, code, reason); unlock(); } void t_phone::pub_end_call(void) { lock(); end_call(); unlock(); } void t_phone::pub_registration(t_user *user, t_register_type register_type, unsigned long expires) { t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) { registration(pu, register_type, expires); } else { log_file->write_header("t_phone::pub_registration", LOG_NORMAL, LOG_WARNING); log_file->write_raw("User profile not active: "); log_file->write_raw(user->get_profile_name()); log_file->write_footer(); } } void t_phone::pub_options(t_user *user, const t_url &to_uri, const string &to_display) { t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) { options(pu, to_uri, to_display); } else { log_file->write_header("t_phone::pub_options", LOG_NORMAL, LOG_WARNING); log_file->write_raw("User profile not active: "); log_file->write_raw(user->get_profile_name()); log_file->write_footer(); } } void t_phone::pub_options(void) { lock(); options(); unlock(); } bool t_phone::pub_hold(void) { lock(); bool retval = hold(); unlock(); return retval; } void t_phone::pub_retrieve(void) { lock(); retrieve(); unlock(); } void t_phone::pub_refer(const t_url &uri, const string &display) { lock(); refer(uri, display); unlock(); } void t_phone::pub_setup_consultation_call(const t_url &uri, const string &display) { lock(); setup_consultation_call(uri, display); unlock(); } void t_phone::pub_refer(unsigned short lineno_from, unsigned short lineno_to) { lock(); refer(lineno_from, lineno_to); unlock(); } void t_phone::mute(bool enable) { lock(); // In a 3-way call, both lines must be muted if (is_3way && ( active_line == line1_3way->get_line_number() || active_line == line2_3way->get_line_number())) { line1_3way->mute(enable); line2_3way->mute(enable); } else { t_rwmutex_reader x(lines_mtx); lines[active_line]->mute(enable); } unlock(); } void t_phone::pub_activate_line(unsigned short l) { lock(); activate_line(l); unlock(); } void t_phone::pub_send_dtmf(char digit, bool inband, bool info) { lock(); send_dtmf(digit, inband, info); unlock(); } bool t_phone::pub_seize(void) { bool retval; t_rwmutex_reader x(lines_mtx); retval = lines[active_line]->seize(); return retval; } bool t_phone::pub_seize(unsigned short line) { assert(line < NUM_USER_LINES); bool retval; t_rwmutex_reader x(lines_mtx); retval = lines[line]->seize(); return retval; } void t_phone::pub_unseize(void) { t_rwmutex_reader x(lines_mtx); lines[active_line]->unseize(); } void t_phone::pub_unseize(unsigned short line) { assert(line < NUM_USER_LINES); t_rwmutex_reader x(lines_mtx); lines[line]->unseize(); } void t_phone::pub_confirm_zrtp_sas(unsigned short line) { assert(line < NUM_USER_LINES); t_rwmutex_reader x(lines_mtx); lines[line]->confirm_zrtp_sas(); } void t_phone::pub_confirm_zrtp_sas(void) { t_rwmutex_reader x(lines_mtx); lines[active_line]->confirm_zrtp_sas(); } void t_phone::pub_reset_zrtp_sas_confirmation(unsigned short line) { assert(line < NUM_USER_LINES); t_rwmutex_reader x(lines_mtx); lines[line]->reset_zrtp_sas_confirmation(); } void t_phone::pub_reset_zrtp_sas_confirmation(void) { t_rwmutex_reader x(lines_mtx); lines[active_line]->reset_zrtp_sas_confirmation(); } void t_phone::pub_enable_zrtp(void) { t_rwmutex_reader x(lines_mtx); lines[active_line]->enable_zrtp(); } void t_phone::pub_zrtp_request_go_clear(void) { t_rwmutex_reader x(lines_mtx); lines[active_line]->zrtp_request_go_clear(); } void t_phone::pub_zrtp_go_clear_ok(unsigned short line) { assert(line < NUM_USER_LINES); t_rwmutex_reader x(lines_mtx); lines[line]->zrtp_go_clear_ok(); } void t_phone::pub_subscribe_mwi(t_user *user) { t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) { pu->subscribe_mwi(); } else { log_file->write_header("t_phone::pub_subscribe_mwi", LOG_NORMAL, LOG_WARNING); log_file->write_raw("User profile not active: "); log_file->write_raw(user->get_profile_name()); log_file->write_footer(); } } void t_phone::pub_unsubscribe_mwi(t_user *user) { t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) { pu->unsubscribe_mwi(); } else { log_file->write_header("t_phone::pub_unsubscribe_mwi", LOG_NORMAL, LOG_WARNING); log_file->write_raw("User profile not active: "); log_file->write_raw(user->get_profile_name()); log_file->write_footer(); } } void t_phone::pub_subscribe_presence(t_user *user) { t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) { pu->subscribe_presence(); } else { log_file->write_header("t_phone::pub_subscribe_presence", LOG_NORMAL, LOG_WARNING); log_file->write_raw("User profile not active: "); log_file->write_raw(user->get_profile_name()); log_file->write_footer(); } } void t_phone::pub_unsubscribe_presence(t_user *user) { t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) { pu->unsubscribe_presence(); } else { log_file->write_header("t_phone::pub_unsubscribe_presence", LOG_NORMAL, LOG_WARNING); log_file->write_raw("User profile not active: "); log_file->write_raw(user->get_profile_name()); log_file->write_footer(); } } void t_phone::pub_publish_presence(t_user *user, t_presence_state::t_basic_state basic_state) { t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) { pu->publish_presence(basic_state); } else { log_file->write_header("t_phone::pub_publish_presence", LOG_NORMAL, LOG_WARNING); log_file->write_raw("User profile not active: "); log_file->write_raw(user->get_profile_name()); log_file->write_footer(); } } void t_phone::pub_unpublish_presence(t_user *user) { t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) { pu->unpublish_presence(); } else { log_file->write_header("t_phone::pub_publish_presence", LOG_NORMAL, LOG_WARNING); log_file->write_raw("User profile not active: "); log_file->write_raw(user->get_profile_name()); log_file->write_footer(); } } bool t_phone::pub_send_message(t_user *user, const t_url &to_uri, const string &to_display, const t_msg &msg) { bool retval = true; t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) { retval = pu->send_message(to_uri, to_display, msg); } else { log_file->write_header("t_phone::pub_send_message", LOG_NORMAL, LOG_WARNING); log_file->write_raw("User profile not active: "); log_file->write_raw(user->get_profile_name()); log_file->write_endl(); log_file->write_footer(); retval = false; } return retval; } bool t_phone::pub_send_im_iscomposing(t_user *user, const t_url &to_uri, const string &to_display, const string &state, time_t refresh) { bool retval = true; t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) { retval = pu->send_im_iscomposing(to_uri, to_display, state, refresh); } else { log_file->write_header("t_phone::pub_send_im_iscomposing", LOG_NORMAL, LOG_WARNING); log_file->write_raw("User profile not active: "); log_file->write_raw(user->get_profile_name()); log_file->write_endl(); log_file->write_footer(); retval = false; } return retval; } t_phone_state t_phone::get_state(void) const { t_rwmutex_reader x(lines_mtx); for (unsigned short i = 0; i < NUM_USER_LINES; i++) { if (lines[i]->get_state() == LS_IDLE) { return PS_IDLE; } } // All lines are busy, so the phone is busy. return PS_BUSY; } bool t_phone::all_lines_idle(void) const { t_rwmutex_reader x(lines_mtx); for (unsigned short i = 0; i < NUM_USER_LINES; i++) { if (lines[i]->get_substate() != LSSUB_IDLE) { return false; } } // All lines are idle return true; } bool t_phone::get_idle_line(unsigned short &lineno) const { t_rwmutex_reader x(lines_mtx); bool found_idle_line = false; for (unsigned short i = 0; i < NUM_USER_LINES; i++) { if (lines[i]->get_substate() == LSSUB_IDLE) { lineno = i; found_idle_line = true; break; } } return found_idle_line; } void t_phone::set_active_line(unsigned short l) { t_rwmutex_reader x(lines_mtx); assert (l < NUM_USER_LINES); active_line = l; } unsigned short t_phone::get_active_line(void) const { return active_line; } t_line *t_phone::get_line_by_id(t_object_id id) const { t_rwmutex_reader x(lines_mtx); for (size_t i = 0; i < lines.size(); i++) { if (lines[i]->get_object_id() == id) { return lines[i]; } } return NULL; } t_line *t_phone::get_line(unsigned short lineno) const { assert(lineno < lines.size()); return lines[lineno]; } bool t_phone::authorize(t_user *user, t_request *r, t_response *resp) { bool result = false; t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) result = pu->authorize(r, resp); return result; } void t_phone::remove_cached_credentials(t_user *user, const string &realm) { t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) pu->remove_cached_credentials(realm); } bool t_phone::get_is_registered(t_user *user) { bool result = false; t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) result = pu->get_is_registered(); return result; } bool t_phone::get_last_reg_failed(t_user *user) { bool result = false; t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) result = pu->get_last_reg_failed(); return result; } t_line_state t_phone::get_line_state(unsigned short lineno) const { assert(lineno < lines.size()); t_rwmutex_reader x(lines_mtx); t_line_state s = get_line(lineno)->get_state(); return s; } t_line_substate t_phone::get_line_substate(unsigned short lineno) const { assert(lineno < lines.size()); t_rwmutex_reader x(lines_mtx); t_line_substate s = get_line(lineno)->get_substate(); return s; } bool t_phone::is_line_on_hold(unsigned short lineno) const { assert(lineno < lines.size()); t_rwmutex_reader x(lines_mtx); bool b = get_line(lineno)->get_is_on_hold(); return b; } bool t_phone::is_line_muted(unsigned short lineno) const { assert(lineno < lines.size()); t_rwmutex_reader x(lines_mtx); bool b = get_line(lineno)->get_is_muted(); return b; } bool t_phone::is_line_transfer_consult(unsigned short lineno, unsigned short &transfer_from_line) const { assert(lineno < lines.size()); t_rwmutex_reader x(lines_mtx); bool b = get_line(lineno)->get_is_transfer_consult(transfer_from_line); return b; } bool t_phone::line_to_be_transferred(unsigned short lineno, unsigned short &transfer_to_line) const { assert(lineno < lines.size()); t_rwmutex_reader x(lines_mtx); bool b = get_line(lineno)->get_to_be_transferred(transfer_to_line); return b; } bool t_phone::is_line_encrypted(unsigned short lineno) const { assert(lineno < lines.size()); t_rwmutex_reader x(lines_mtx); bool b = get_line(lineno)->get_is_encrypted(); return b; } bool t_phone::is_line_auto_answered(unsigned short lineno) const { assert(lineno < lines.size()); t_rwmutex_reader x(lines_mtx); bool b = get_line(lineno)->get_auto_answer(); return b; } t_refer_state t_phone::get_line_refer_state(unsigned short lineno) const { assert(lineno < lines.size()); t_rwmutex_reader x(lines_mtx); t_refer_state s = get_line(lineno)->get_refer_state(); return s; } t_user *t_phone::get_line_user(unsigned short lineno) { assert(lineno < lines.size()); t_rwmutex_reader x(lines_mtx); t_user *user = get_line(lineno)->get_user(); return user; } bool t_phone::has_line_media(unsigned short lineno) const { assert(lineno < lines.size()); t_rwmutex_reader x(lines_mtx); bool b = get_line(lineno)->has_media(); return b; } bool t_phone::is_mwi_subscribed(t_user *user) const { bool result = false; t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) result = pu->is_mwi_subscribed(); return result; } bool t_phone::is_mwi_terminated(t_user *user) const { bool result = false; t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) result = pu->is_mwi_terminated(); return result; } t_mwi t_phone::get_mwi(t_user *user) const { t_mwi result; t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) result = pu->mwi; return result; } bool t_phone::is_presence_terminated(t_user *user) const { bool result = false; t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) result = pu->is_presence_terminated(); return result; } t_url t_phone::get_remote_uri(unsigned short lineno) const { assert(lineno < lines.size()); t_rwmutex_reader x(lines_mtx); t_url uri = get_line(lineno)->get_remote_uri(); return uri; } string t_phone::get_remote_display(unsigned short lineno) const { assert(lineno < lines.size()); t_rwmutex_reader x(lines_mtx); string display = get_line(lineno)->get_remote_display(); return display; } bool t_phone::part_of_3way(unsigned short lineno) { t_mutex_guard x(mutex_3way); if (!is_3way) { return false; } if (line1_3way->get_line_number() == lineno) { return true; } if (line2_3way->get_line_number() == lineno) { return true; } return false; } t_line *t_phone::get_3way_peer_line(unsigned short lineno) { t_mutex_guard x(mutex_3way); if (!is_3way) { return NULL; } if (line1_3way->get_line_number() == lineno) { return line2_3way; } return line1_3way; } bool t_phone::join_3way(unsigned short lineno1, unsigned short lineno2) { assert(lineno1 < NUM_USER_LINES); assert(lineno2 < NUM_USER_LINES); t_mutex_guard x3(mutex_3way); t_rwmutex_reader x(lines_mtx); // Check if there isn't a 3-way already if (is_3way) { return false; } // Both lines must have a call. if (lines[lineno1]->get_substate() != LSSUB_ESTABLISHED || lines[lineno2]->get_substate() != LSSUB_ESTABLISHED) { return false; } // One of the lines must be on-hold t_line *held_line, *talking_line; if (lines[lineno1]->get_is_on_hold()) { held_line = lines[lineno1]; talking_line = lines[lineno2]; } else if (lines[lineno2]->get_is_on_hold()) { held_line = lines[lineno2]; talking_line = lines[lineno1]; } else { return false; } // Set 3-way data is_3way = true; line1_3way = talking_line; line2_3way = held_line; // The user may have put both lines on-hold. In this case the // talking line is on-hold too! if (talking_line->get_is_on_hold()) { // Retrieve the held call // As the 3-way indication (is_3way) is set, the audio sessions // will automatically connect to each other. talking_line->retrieve(); } else { // Start the 3-way on the talking line t_audio_session *as_talking = talking_line->get_audio_session(); if (as_talking) as_talking->start_3way(); } // Retrieve the held call held_line->retrieve(); return true; } void t_phone::notify_refer_progress(t_response *r, unsigned short referee_lineno) { t_rwmutex_reader x(lines_mtx); if (lines[LINENO_REFERRER]->get_state() != LS_IDLE) { lines[LINENO_REFERRER]->notify_refer_progress(r); if (!lines[LINENO_REFERRER]->active_dialog || lines[LINENO_REFERRER]->active_dialog->get_state() != DS_CONFIRMED) { // The call to the referrer has already been // terminated. return; } if (r->is_final()) { if (r->is_success()) { // Reference was successful, end the call with // with the referrer. log_file->write_header( "t_phone::notify_refer_progress"); log_file->write_raw( "Call to refer-target succeeded.\n"); log_file->write_raw( "End call with referrer.\n"); log_file->write_footer(); lines[LINENO_REFERRER]->end_call(); } else { // Reference failed, retrieve the call with the // referrer. log_file->write_header( "t_phone::notify_refer_progress"); log_file->write_raw( "Call to refer-target failed.\n"); log_file->write_raw( "Restore call with referrer.\n"); log_file->write_footer(); // Retrieve the parked line t_line *l = lines[referee_lineno]; lines[referee_lineno] = lines[LINENO_REFERRER]; lines[referee_lineno]->line_number = referee_lineno; lines[LINENO_REFERRER] = l; lines[LINENO_REFERRER]->line_number = LINENO_REFERRER; // Retrieve the call if the line is active if (referee_lineno == active_line) { log_file->write_report( "Retrieve call with referrer.", "t_phone::notify_refer_progress"); lines[referee_lineno]->retrieve(); } t_user *user_config = lines[referee_lineno]->get_user(); assert(user_config); ui->cb_retrieve_referrer(user_config, referee_lineno); } } } } t_call_info t_phone::get_call_info(unsigned short lineno) const { assert(lineno < lines.size()); t_rwmutex_reader x(lines_mtx); t_call_info call_info = get_line(lineno)->get_call_info(); return call_info; } t_call_record t_phone::get_call_hist(unsigned short lineno) const { assert(lineno < lines.size()); t_rwmutex_reader x(lines_mtx); t_call_record call_hist = get_line(lineno)->call_hist_record; return call_hist; } string t_phone::get_ringtone(unsigned short lineno) const { assert(lineno < lines.size()); t_rwmutex_reader x(lines_mtx); string ringtone = get_line(lineno)->get_ringtone(); return ringtone; } time_t t_phone::get_startup_time(void) const { return startup_time; } void t_phone::init_rtp_ports(void) { t_rwmutex_reader x(lines_mtx); for (size_t i = 0; i < lines.size(); i++) { lines[i]->init_rtp_port(); } } bool t_phone::add_phone_user(const t_user &user_config, t_user **dup_user) { t_phone_user *existing_phone_user = NULL; t_rwmutex_writer x(phone_users_mtx); for (list::iterator i = phone_users.begin(); i != phone_users.end(); i++) { t_user *user = (*i)->get_user_profile(); // If the profile is already added, then just activate it. if (user->get_profile_name() == user_config.get_profile_name()) { existing_phone_user = (*i); // Continue checking to see if activating this user // does not conflict with another already active user. continue; } // Check if there is already another profile for the same // user. if (user->get_name() == user_config.get_name() && user->get_domain() == user_config.get_domain() && (*i)->is_active()) { *dup_user = user; return false; } // Check if there is already another profile having // the same contact name. if (user->get_contact_name() == user_config.get_contact_name() && USER_HOST(user, AUTO_IP4_ADDRESS) == USER_HOST(&user_config, AUTO_IP4_ADDRESS) && (*i)->is_active()) { *dup_user = user; return false; } } // Activate existing profile if (existing_phone_user) { if (!existing_phone_user->is_active()) { existing_phone_user->activate(user_config); } return true; } // Add the user t_phone_user *pu = new t_phone_user(user_config); MEMMAN_NEW(pu); phone_users.push_back(pu); return true; } void t_phone::remove_phone_user(const t_user &user_config) { t_rwmutex_writer x(phone_users_mtx); t_phone_user *pu = find_phone_user(user_config.get_profile_name()); if (pu) pu->deactivate(); } list t_phone::ref_users(void) { list l; t_rwmutex_reader x(phone_users_mtx); for (list::iterator i = phone_users.begin(); i != phone_users.end(); i++) { if (!(*i)->is_active()) continue; l.push_back((*i)->get_user_profile()); } return l; } t_user *t_phone::ref_user_display_uri(const string &display_uri) { t_user *u = NULL; t_rwmutex_reader x(phone_users_mtx); for (list::iterator i = phone_users.begin(); i != phone_users.end(); i++) { if (!(*i)->is_active()) continue; if ((*i)->get_user_profile()->get_display_uri() == display_uri) { u = (*i)->get_user_profile(); break; } } return u; } t_user *t_phone::ref_user_profile(const string &profile_name) { t_user *u = NULL; t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(profile_name); if (pu) u = pu->get_user_profile(); return u; } t_service *t_phone::ref_service(t_user *user) { assert(user); t_service *srv = NULL; t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) srv = pu->service; return srv; } t_buddy_list *t_phone::ref_buddy_list(t_user *user) { assert(user); t_buddy_list *l = NULL; t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) l = pu->get_buddy_list(); return l; } t_presence_epa *t_phone::ref_presence_epa(t_user *user) { assert(user); t_presence_epa *epa = NULL; t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) epa = pu->get_presence_epa(); return epa; } string t_phone::get_ip_sip(const t_user *user, const string &auto_ip) const { string result; t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) { result = pu->get_ip_sip(auto_ip); } else { result = LOCAL_IP; } if (result == AUTO_IP4_ADDRESS) result = auto_ip; return result; } unsigned short t_phone::get_public_port_sip(const t_user *user) const { unsigned short result; t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) { result = pu->get_public_port_sip(); } else { result = sys_config->get_sip_port(); } return result; } bool t_phone::use_stun(t_user *user) { bool result; t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) { result = pu->use_stun; } else { result = false; } return result; } bool t_phone::use_nat_keepalive(t_user *user) { bool result; t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) { result = pu->use_nat_keepalive; } else { result = false; } return result; } void t_phone::disable_stun(t_user *user) { t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) pu->use_stun = false; } void t_phone::sync_nat_keepalive(t_user *user) { t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) pu->sync_nat_keepalive(); } bool t_phone::stun_discover_nat(list &msg_list) { bool retval = true; t_rwmutex_reader x(phone_users_mtx); for (list::iterator i = phone_users.begin(); i != phone_users.end(); ++i) { if (!(*i)->is_active()) continue; t_user *user_config = (*i)->get_user_profile(); if (user_config->get_sip_transport() == SIP_TRANS_UDP || user_config->get_sip_transport() == SIP_TRANS_AUTO) { if (user_config->get_use_stun()) { string msg; if (!::stun_discover_nat(*i, msg)) { string s("User profile: "); s + user_config->get_profile_name(); s += "\n\n"; s += msg; msg_list.push_back(s); retval = false; } } else { (*i)->use_nat_keepalive = user_config->get_enable_nat_keepalive(); } } } return retval; } bool t_phone::stun_discover_nat(t_user *user, string &msg) { bool retval = true; t_rwmutex_reader x(phone_users_mtx); if (user->get_sip_transport() == SIP_TRANS_UDP || user->get_sip_transport() == SIP_TRANS_AUTO) { t_phone_user *pu = find_phone_user(user->get_profile_name()); if (user->get_use_stun()) { if (pu) retval = ::stun_discover_nat(pu, msg); } else { if (pu) pu->use_nat_keepalive = user->get_enable_nat_keepalive(); } } return retval; } t_response *t_phone::create_options_response(t_user *user, t_request *r, bool in_dialog) { t_response *resp; t_rwmutex_reader x(phone_users_mtx); t_phone_user *pu = find_phone_user(user->get_profile_name()); if (pu) { resp = pu->create_options_response(r, in_dialog); } else { resp = r->create_response(R_500_INTERNAL_SERVER_ERROR); } return resp; } void t_phone::init(void) { list user_list = ref_users(); for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { // Automatic registration at startup if requested if ((*i)->get_register_at_startup()) { pub_registration(*i, REG_REGISTER, DUR_REGISTRATION(*i)); } else { // No registration will be done, so initialize extensions now. init_extensions(*i); } // NOTE: Extension initialization is done after registration. // This way STUN will have set the correct // IP adres (STUN is done as part of registration.) } } void t_phone::init_extensions(t_user *user_config) { // Subscribe to MWI if (user_config->get_mwi_sollicited()) { pub_subscribe_mwi(user_config); } // Publish presence if (user_config->get_pres_publish_startup()) { pub_publish_presence(user_config, t_presence_state::ST_BASIC_OPEN); } // Subscribe to presence pub_subscribe_presence(user_config); } bool t_phone::set_sighandler(void) const { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = phone_sighandler; sa.sa_flags = SA_RESTART; if (sigaction (SIGCHLD, &sa, NULL) < 0) return false; if (sigaction (SIGTERM, &sa, NULL) < 0) return false; if (sigaction (SIGINT, &sa, NULL) < 0) return false; return true; } void t_phone::terminate(void) { string msg; lines_mtx.lockRead(); // Clear all lines log_file->write_report("Clear all lines.", "t_phone::terminate", LOG_NORMAL, LOG_DEBUG); for (size_t i = 0; i < NUM_CALL_LINES; i++) { switch (lines[i]->get_substate()) { case LSSUB_IDLE: case LSSUB_RELEASING: break; case LSSUB_SEIZED: lines[i]->unseize(); break; case LSSUB_INCOMING_PROGRESS: ui->cb_stop_call_notification(i); lines[i]->reject(); break; case LSSUB_OUTGOING_PROGRESS: ui->cb_stop_call_notification(i); // Fall thru case LSSUB_ANSWERING: case LSSUB_ESTABLISHED: lines[i]->end_call(); break; } } lines_mtx.unlock(); // Deactivate phone is_active = false; // De-register all registered users. list user_list = ref_users(); ui->cb_display_msg("Deregistering phone..."); for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { // Unsubscribe MWI if (is_mwi_subscribed(*i)) { msg = (*i)->get_profile_name(); msg += ": Unsubscribe MWI."; log_file->write_report(msg, "t_phone::terminate", LOG_NORMAL, LOG_DEBUG); pub_unsubscribe_mwi(*i); } // Unpublish presence pub_unpublish_presence(*i); // Unsubscribe presence pub_unsubscribe_presence(*i); // De-register if (get_is_registered(*i)) { msg = (*i)->get_profile_name(); msg += ": Deregister."; log_file->write_report(msg, "t_phone::terminate", LOG_NORMAL, LOG_DEBUG); pub_registration(*i, REG_DEREGISTER); } } // Wait till phone is deregistered. for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { while (get_is_registered(*i)) { sleep(1); } msg = (*i)->get_profile_name(); msg += ": Registration terminated."; log_file->write_report(msg, "t_phone::terminate", LOG_NORMAL, LOG_DEBUG); } // Wait for MWI unsubscription int mwi_wait = 0; for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { while (!is_mwi_terminated(*i) && mwi_wait <= DUR_UNSUBSCRIBE_GUARD/1000) { sleep(1); mwi_wait++; } msg = (*i)->get_profile_name(); msg += ": MWI subscription terminated."; log_file->write_report(msg, "t_phone::terminate", LOG_NORMAL, LOG_DEBUG); } // Wait for presence unsubscription int presence_wait = 0; for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { while (!is_presence_terminated(*i) && presence_wait <= DUR_UNSUBSCRIBE_GUARD/1000) { sleep(1); presence_wait++; } msg = (*i)->get_profile_name(); msg += ": presence subscriptions terminated."; log_file->write_report(msg, "t_phone::terminate", LOG_NORMAL, LOG_DEBUG); } // Wait till all lines are idle log_file->write_report("Waiting for all lines to become idle.", "t_phone::terminate", LOG_NORMAL, LOG_DEBUG); int dur = 0; while (dur < QUIT_IDLE_WAIT) { if (all_lines_idle()) break; sleep(1); dur++; } // Force lines to idle state if they could not be cleared // gracefully lines_mtx.lockRead(); for (size_t i = 0; i < lines.size(); i++) { if (lines[i]->get_substate() != LSSUB_IDLE) { msg = "Force line %1 to idle state."; msg = replace_first(msg, "%1", int2str(i)); log_file->write_report(msg, "t_phone::terminate", LOG_NORMAL, LOG_DEBUG); lines[i]->force_idle(); } } lines_mtx.unlock(); log_file->write_report("Finished phone termination.", "t_phone::terminate", LOG_NORMAL, LOG_DEBUG); } void *phone_uas_main(void *arg) { phone->run(); return NULL; } void *phone_sigwait(void *arg) { sigset_t sigset; int sig; int child_status; pid_t pid; sigemptyset(&sigset); sigaddset(&sigset, SIGINT); sigaddset(&sigset, SIGTERM); sigaddset(&sigset, SIGCHLD); while (true) { // When SIGCONT is received after SIGSTOP, sigwait returns // with EINTR ?? if (sigwait(&sigset, &sig) == EINTR) continue; switch (sig) { case SIGINT: log_file->write_report("SIGINT received.", "::phone_sigwait"); ui->cmd_quit(); return NULL; case SIGTERM: log_file->write_report("SIGTERM received.", "::phone_sigwait"); ui->cmd_quit(); return NULL; case SIGCHLD: // Cleanup terminated child process pid = wait(&child_status); log_file->write_header("::phone_sigwait"); log_file->write_raw("SIGCHLD received.\n"); log_file->write_raw("Pid "); log_file->write_raw((int)pid); log_file->write_raw(" terminated.\n"); log_file->write_footer(); break; default: log_file->write_header("::phone_sigwait", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Unexpected signal ("); log_file->write_raw(sig); log_file->write_raw(") received.\n"); log_file->write_footer(); } } } void phone_sighandler(int sig) { int child_status; pid_t pid; // Minimal processing should be done in a signal handler. // No I/O should be performed. switch (sig) { case SIGINT: // Post a quit command instead of executing it. As executing // involves a lock that may lead to a deadlock. ui->cmd_quit_async(); break; case SIGTERM: ui->cmd_quit_async(); break; case SIGCHLD: // Cleanup terminated child process pid = wait(&child_status); break; } } twinkle-1.10.1/src/phone.h000066400000000000000000000567161277565361200153550ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _PHONE_H #define _PHONE_H #include #include #include #include #include "auth.h" #include "call_history.h" #include "dialog.h" #include "id_object.h" #include "phone_user.h" #include "protocol.h" #include "service.h" #include "transaction_layer.h" #include "im/msg_session.h" #include "mwi/mwi.h" #include "sockets/url.h" #include "parser/request.h" #include "parser/response.h" #include "presence/presence_state.h" // Number of phone lines // One line is used by Twinkle internally to park the call towards a // referrer while the refer is in progress. // Besides the lines for making calls, ephemeral lines will be created // for parking calls that are being released. By parking a releasing // call, the line visible to the user is free for making new calls. #define NUM_CALL_LINES 3 // Total numbers of phone lines for making calls #define NUM_USER_LINES 2 // #lines usable for the user #define LINENO_REFERRER 2 // Internal lineno for referrer // Number of seconds to wait till all lines are idle when terminating // Twinkle #define QUIT_IDLE_WAIT 2 using namespace std; using namespace im; // Forward declarations class t_dialog; class t_client_request; class t_line; class t_call_info; enum t_phone_state { PS_IDLE, PS_BUSY }; enum t_line_state { LS_IDLE, LS_BUSY }; enum t_line_substate { // Idle sub states LSSUB_IDLE, // line is idle LSSUB_SEIZED, // user has seized the line to call // Busy sub states LSSUB_INCOMING_PROGRESS, // incoming call in progress LSSUB_OUTGOING_PROGRESS, // outgoing call in progress LSSUB_ANSWERING, // sent 200 OK, waiting for ACK LSSUB_ESTABLISHED, // call established LSSUB_RELEASING // call is being released (BYE sent) }; class t_transfer_data { private: // The received REFER request t_request *refer_request; // Line number on which REFER was received unsigned short lineno; // Indicates if triggered INVITE must be anonymous bool hide_user; t_phone_user *phone_user; public: t_transfer_data(t_request *r, unsigned short _lineno, bool _hide_user, t_phone_user *pu); ~t_transfer_data(); t_request *get_refer_request(void) const; unsigned short get_lineno(void) const; bool get_hide_user(void) const; t_phone_user *get_phone_user(void) const; }; class t_phone : public t_transaction_layer { private: // Indicates if the phone is active, accepting calls. bool is_active; // Phone users list phone_users; mutable t_rwmutex phone_users_mtx; // Phone lines // The first NUM_CALL_LINES are for making phone calls. // The tail of the vector is for releasing lines in the background. vector lines; mutable t_rwmutex lines_mtx; // Operations like invite, end_call work on the active line unsigned short active_line; // 3-way conference data bool is_3way; // indicates an acitive 3-way t_line *line1_3way; // first line in 3-way conf t_line *line2_3way; // second line in 3-way conf mutable t_mutex mutex_3way; // Call transfer data. When a REFER comes in, the user has // to give permission before the triggered INVITE can be sent. // While the user interface presents the question to the user, // the data related to the incoming REFER is stored here. t_transfer_data *incoming_refer_data; // Time of startup time_t startup_time; // Line release operations // Move a line to the background so it will be released in the // background. void move_line_to_background(unsigned short lineno); // Move all call lines that are in releasing state to the // background. void move_releasing_lines_to_background(void); // Destroy lines in the background that are idle. void cleanup_dead_lines(void); // If a line was part of a 3way, then remove it from the // 3way conference data. void cleanup_3way_state(unsigned short lineno); // If one of the lines of a 3way calls has become idle, then // cleanup the 3way conference data. void cleanup_3way(void); /** @name Actions */ //@{ /** * Send an INVITE * @param pu The phone user making this call. * @param to_uri The URI to be used a request-URI and To header URI * @param to_display Display name for To header. * @param subject If not empty, this string will go into the Subject header. * @param no_fork If true, put a no-fork request disposition in the outgoing INVITE * @param anonymous Inidicates if the INVITE should be sent anonymous. */ void invite(t_phone_user *pu, const t_url &to_uri, const string &to_display, const string &subject, bool no_fork, bool anonymous); void answer(void); void redirect(const list &destinations, int code, string reason = ""); void reject(void); void reject(unsigned short line); void end_call(void); void registration(t_phone_user *pu, t_register_type register_type, unsigned long expires = 0); //@} // OPTIONS outside dialog void options(t_phone_user *pu, const t_url &to_uri, const string &to_display = ""); // OPTIONS inside dialog void options(void); bool hold(bool rtponly = false); // returns false is call cannot be put on hold void retrieve(void); // Transfer a call (send REFER to far-end) void refer(const t_url &uri, const string &display); // Call transfer with consultation (attended) // Transfer the far-end on line lineno_from to the far-end of lineno_to. void refer(unsigned short lineno_from, unsigned short lineno_to); void refer_attended(unsigned short lineno_from, unsigned short lineno_to); void refer_consultation(unsigned short lineno_from, unsigned short lineno_to); // Setup a consultation call for transferring the call on the active // line. The active line is put on-hold and the consultation call is // made on an idle line. void setup_consultation_call(const t_url &uri, const string &display); // Make line l active. If the current line is busy, then that call // will be put on-hold. If line l has a call on-hold, then that // call will be retrieved. void activate_line(unsigned short l); // Send a DTMF digit void send_dtmf(char digit, bool inband, bool info); void set_active_line(unsigned short l); // Handle responses for out-of-dialog requests void handle_response_out_of_dialog(t_response *r, t_tuid tuid, t_tid tid); void handle_response_out_of_dialog(StunMessage *r, t_tuid tuid); // Match an incoming message to a phone user t_phone_user *match_phone_user(t_response *r, t_tuid tuid, bool active_only = false); t_phone_user *match_phone_user(t_request *r, bool active_only = false); t_phone_user *match_phone_user(StunMessage *r, t_tuid tuid, bool active_only = false); /** * Hunt for an idle line to hande an incoming call. * @return The number of the line to handle the call (starting at 0). * @return -1 if there is no line to handle the call. */ int hunt_line(void); protected: /** * Find a phone user that can handle an out-of-dialog request. * If there is no phone user that can handle the request, then this * method will send an appropriate failure response on the request. * @param r [in] The request. * @param tid [in] Transaction id of the request transaction. * @return The phone user, if there is a phone user that can handle the request. * @return NULL, otherwise. */ t_phone_user *find_phone_user_out_dialog_request(t_request *r, t_tid tid); /** * Find a line that can handle an in-dialog request. * If there is no line that can handle the request, then this * method will send an appropriate failure response on the request. * @param r [in] The request. * @param tid [in] Transaction id of the request transaction. * @return The line, if there is a line that can handle the request. * @return NULL, otherwise. */ t_line *find_line_in_dialog_request(t_request *r, t_tid tid); // Events void recvd_provisional(t_response *r, t_tuid tuid, t_tid tid); void recvd_success(t_response *r, t_tuid tuid, t_tid tid); void recvd_redirect(t_response *r, t_tuid tuid, t_tid tid); void recvd_client_error(t_response *r, t_tuid tuid, t_tid tid); void recvd_server_error(t_response *r, t_tuid tuid, t_tid tid); void recvd_global_error(t_response *r, t_tuid tuid, t_tid tid); void post_process_response(t_response *r, t_tuid tuid, t_tid tid); void recvd_invite(t_request *r, t_tid tid); void recvd_initial_invite(t_request *r, t_tid tid); void recvd_re_invite(t_request *r, t_tid tid); void recvd_ack(t_request *r, t_tid tid); void recvd_cancel(t_request *r, t_tid cancel_tid, t_tid target_tid); void recvd_bye(t_request *r, t_tid tid); void recvd_options(t_request *r, t_tid tid); void recvd_register(t_request *r, t_tid tid); void recvd_prack(t_request *r, t_tid tid); void recvd_subscribe(t_request *r, t_tid tid); void recvd_notify(t_request *r, t_tid tid); void recvd_refer(t_request *r, t_tid tid); void recvd_info(t_request *r, t_tid tid); void recvd_message(t_request *r, t_tid tid); void post_process_request(t_request *r, t_tid cancel_tid, t_tid target_tid); void failure(t_failure failure, t_tid tid); void recvd_stun_resp(StunMessage *r, t_tuid tuid, t_tid tid); void recvd_refer_permission(bool permission); /** @name Timeout handlers */ //@{ virtual void handle_event_timeout(t_event_timeout *e); /** * Process expiry of line timer. * @param id [in] Line id of the line associate with the timer. * @param timer [in] Type of line timer. * @param did [in] Dialog id if timer is for a dialog, 0 otherwise. */ void line_timeout(t_object_id id, t_line_timer timer, t_object_id did); /** * Process expiry of a line subscription timer (REFER subscription). * @param id [in] Line id of the line associate with the timer. * @param timer [in] Type of subcription timer. * @param did [in] Dialog id associated with the timer. * @param event_type [in] Event type of the subscription. * @param event_id [in] Event id of the subscription. */ void line_timeout_sub(t_object_id id, t_subscribe_timer timer, t_object_id did, const string &event_type, const string &event_id); /** * Process expiry of a subcription timer. * @param timer [in] Type of subcription timer. * @param id_timer [in] Timer id of expired timer. */ void subscription_timeout(t_subscribe_timer timer, t_object_id id_timer); /** * Process expiry of a publication timer. * @param timer [in] Type of publication timer. * @param id_timer [in] Timer id of expired timer. */ void publication_timeout(t_publish_timer timer, t_object_id id_timer); /** * Process expiry of phone timer. * @param timer [in] Type of phone timer. * @param id_timer [in] Timer id of expired timer. */ void timeout(t_phone_timer timer, unsigned short id_timer); //@} virtual void handle_broken_connection(t_event_broken_connection *e); public: t_phone(); virtual ~t_phone(); // Get line based on object id // Returns NULL if there is no such line. t_line *get_line_by_id(t_object_id id) const; // Get line based on line number t_line *get_line(unsigned short lineno) const; // Get busy/idle state of the phone // PS_IDLE - at least one line is idle // PS_BUSY - all lines are busy t_phone_state get_state(void) const; // Returns true if all lines are in the LSSUB_IDLE state bool all_lines_idle(void) const; // Get an idle user line. // If no line is idle, then false is returned. bool get_idle_line(unsigned short &lineno) const; // Actions to be called by the user interface. // These methods first lock the phone, then call the corresponding // private method and then unlock the phone. // The private methods should only be called by the phone, line, // and dialog objects to avoid deadlocks. void pub_invite(t_user *user, const t_url &to_uri, const string &to_display, const string &subject, bool anonymous); void pub_answer(void); void pub_reject(void); void pub_reject(unsigned short line); void pub_redirect(const list &destinations, int code, string reason = ""); void pub_end_call(void); void pub_registration(t_user *user, t_register_type register_type, unsigned long expires = 0); void pub_options(t_user *user, const t_url &to_uri, const string &to_display = ""); void pub_options(void); bool pub_hold(void); void pub_retrieve(void); void pub_refer(const t_url &uri, const string &display); void pub_refer(unsigned short lineno_from, unsigned short lineno_to); void pub_setup_consultation_call(const t_url &uri, const string &display); void mute(bool enable); void pub_activate_line(unsigned short l); void pub_send_dtmf(char digit, bool inband, bool info); // ZRTP actions void pub_confirm_zrtp_sas(unsigned short line); void pub_confirm_zrtp_sas(void); void pub_reset_zrtp_sas_confirmation(unsigned short line); void pub_reset_zrtp_sas_confirmation(void); void pub_enable_zrtp(void); void pub_zrtp_request_go_clear(void); void pub_zrtp_go_clear_ok(unsigned short line); // Join 2 lines in a 3-way conference. Returns false if 3-way cannot // be setup bool join_3way(unsigned short lineno1, unsigned short lineno2); // Seize the line. // Returns false if seizure failed. bool pub_seize(void); // active line bool pub_seize(unsigned short line); // Unseize the line void pub_unseize(void); // active line void pub_unseize(unsigned short line); /** @name MWI */ //@{ /** * Subscribe to MWI. * @param user [in] The user profile of the subscribing user. */ void pub_subscribe_mwi(t_user *user); /** * Unsubscribe to MWI. * @param user [in] The user profile of the unsubscribing user. */ void pub_unsubscribe_mwi(t_user *user); //@} /** @name Presence */ //@{ /** * Subscribe to presence of buddies in buddy list. * @param user [in] The user profile of the subscribing user. */ void pub_subscribe_presence(t_user *user); /** * Unsubscribe to presence of buddies in buddy list. * @param user [in] The user profile of the unsubscribing user. */ void pub_unsubscribe_presence(t_user *user); /** * Publish presence state. * @param user [in] The user profile of the user publishing. * @param basic_state [in] The basic presence state to publish. */ void pub_publish_presence(t_user *user, t_presence_state::t_basic_state basic_state); /** * Unpublish presence state. * @param user [in] The user profile of the user unpublishing. */ void pub_unpublish_presence(t_user *user); //@} /** @name Instant messaging */ //@{ /** * Send a message. * @param user [in] User profile of user sending the message. * @param to_uri [in] Destination URI of recipient. * @param to_display [in] Display name of recipient. * @param msg [in] Message to send. * @return True if sending succeeded, false otherwise. */ bool pub_send_message(t_user *user, const t_url &to_uri, const string &to_display, const t_msg &msg); /** * Send a message composing state indication. * @param user [in] User profile of user sending the message. * @param to_uri [in] Destination URI of recipient. * @param to_display [in] Display name of recipient. * @param state [in] Message composing state. * @param refresh [in] The refresh interval in seconds (when state is active). * @return True if sending succeeded, false otherwise. * @note For the idle state, the value of refresh has no meaning. */ bool pub_send_im_iscomposing(t_user *user, const t_url &to_uri, const string &to_display, const string &state, time_t refresh); //@} unsigned short get_active_line(void) const; // Authorize the request based on the challenge in the response // Returns false if authorization fails. bool authorize(t_user *user, t_request *r, t_response *resp); // Remove cached credentials for a particular user/realm void remove_cached_credentials(t_user *user, const string &realm); bool get_is_registered(t_user *user); bool get_last_reg_failed(t_user *user); t_line_state get_line_state(unsigned short lineno) const; t_line_substate get_line_substate(unsigned short lineno) const; bool is_line_on_hold(unsigned short lineno) const; bool is_line_muted(unsigned short lineno) const; bool is_line_transfer_consult(unsigned short lineno, unsigned short &transfer_from_line) const; bool line_to_be_transferred(unsigned short lineno, unsigned short &transfer_to_line) const; bool is_line_encrypted(unsigned short lineno) const; bool is_line_auto_answered(unsigned short lineno) const; t_refer_state get_line_refer_state(unsigned short lineno) const; t_user *get_line_user(unsigned short lineno); bool has_line_media(unsigned short lineno) const; bool is_mwi_subscribed(t_user *user) const; bool is_mwi_terminated(t_user *user) const; t_mwi get_mwi(t_user *user) const; /** * Check if all presence subscriptions for a particular user are terminated. * @param user [in] User profile of the user. * @return True if all presence susbcriptions are terminated, otherwise false. */ bool is_presence_terminated(t_user *user) const; // Get remote uri/display of the active call on a line. // If there is no call, then an empty uri/display is returned. t_url get_remote_uri(unsigned short lineno) const; string get_remote_display(unsigned short lineno) const; // Return if a line is part of a 3-way conference bool part_of_3way(unsigned short lineno); // Get the peer line in a 3-way conference t_line *get_3way_peer_line(unsigned short lineno); // Notify progress of a reference. r is the response to the INVITE // caused by a REFER. referee_lineno is the line number of the line // that is setting up there reference call. void notify_refer_progress(t_response *r, unsigned short referee_lineno); // Get call info record for a line. t_call_info get_call_info(unsigned short lineno) const; // Get the call history record for a line t_call_record get_call_hist(unsigned short lineno) const; // Get ring tone for a line string get_ringtone(unsigned short lineno) const; // Get the startup time of the phone time_t get_startup_time(void) const; // Initialize the RTP port values for all lines. void init_rtp_ports(void); /** * Add a phone user. * @param user_config [in] User profile of the user to add. * @param dup_user [out] Profile of duplicate user. * @return false, if there is already a phone user with the same name * and domain. In this case dup_user is a pointer to the user config * of that user. * @return true, if the phone user was added successfully. * @note if there is already a user with exactly the same user config * then true is returned, but the user is not added again. The user * will be activated if it was inactive though. */ bool add_phone_user(const t_user &user_config, t_user **dup_user); /** * Deactivate the phone user. * @param user_config [in] User profile of the user to deactivate. */ void remove_phone_user(const t_user &user_config); /** * Get a list of user profiles of all phone users. * @return List of user profiles. */ list ref_users(void); /** * Get the user profile of a user for which user->get_display_uri() == * display_uri. * @param display_uri [in] Display URI. * @return User profile. */ t_user *ref_user_display_uri(const string &display_uri); /** * Get the user profile matching the profile name. * @param profile_name [in] User profile name. * @return User profile. */ t_user *ref_user_profile(const string &profile_name); /** * Get service information for a phone user. * @param user [in] User profile of the phone user. * @return Service object. */ t_service *ref_service(t_user *user); /** * Get the buddy list of a phone user. * @param user [in] User profile of the phone user. * @return Buddy list. */ t_buddy_list *ref_buddy_list(t_user *user); /** * Get the presence event publication agent of a phone user. * @param user [in] User profile of the phone user. * @return The presence EPA. */ t_presence_epa *ref_presence_epa(t_user *user); /** * Find active phone user * @param profile_name [in] User profile name. * @return The phone user for the user profile, NULL if there is no active phone user. */ t_phone_user *find_phone_user(const string &profile_name) const; /** * Find active phone user * @param user_uri [in] The user URI (AoR) of the user to find. * @return The phone user for the URI, NULL if there is no active phone user. */ t_phone_user *find_phone_user(const t_url &user_uri) const; /** * Get local IP address for SIP. * @param user [in] The user profile of the user for whom to get the IP address. * @param auto_ip [in] IP address to use if no IP address has been determined through * some NAT procedure. * @return The IP address. */ string get_ip_sip(const t_user *user, const string &auto_ip) const; /** * Get local port for SIP. * @param user [in] User profile for user for whom to get the port. * @return SIP port. */ unsigned short get_public_port_sip(const t_user *user) const; /** Indicates if STUN is used. */ bool use_stun(t_user *user); // Indicates if a NAT keepalive mechanism is used bool use_nat_keepalive(t_user *user); /** Disable STUN for a user. */ void disable_stun(t_user *user); /** Synchronize sending of NAT keep alives with user configuration settings. */ void sync_nat_keepalive(t_user *user); // Perform NAT discovery for all users having STUN enabled. // If NAT discovery indicates that STUN cannot be used for 1 or more // users, then false will be returned and msg_list contains a list // of messages to be shown to the user. bool stun_discover_nat(list &msg_list); // Perform NAT discovery for a single user. bool stun_discover_nat(t_user *user, string &msg); // Create a response to an OPTIONS request // Argument 'in-dialog' indicates if the OPTIONS response is // sent within a dialog. t_response *create_options_response(t_user *user, t_request *r, bool in_dialog = false); // Timer operations void start_timer(t_phone_timer timer, t_phone_user *pu); void stop_timer(t_phone_timer timer, t_phone_user *pu); // Start a timer with the time set in the time-argument. void start_set_timer(t_phone_timer timer, long time, t_phone_user *pu); /** * Initialize the phone functions. * Register all active users with auto register. * Initialize extensions for users without auto register. */ void init(void); /** * Initialize SIP extensions like MWI and presence. * @param user_config [in] User for which the extensions must be initialized. */ void init_extensions(t_user *user_config); /** * Set the signal handler to handler for LinuxThreads. * @return True if successful, false otherwise. */ bool set_sighandler(void) const; /** * Terminate the phone functions. * Release all calls, don't accept any new calls. * Deregister all active users. */ void terminate(void); }; // Main function for the UAS part of the phone void *phone_uas_main(void *arg); // Entry function of thread catching signals to terminate // the application in a graceful manner if NPLT is used. void *phone_sigwait(void *arg); // Signal handler to process signals if LinuxThreads is used. void phone_sighandler(int sig); #endif twinkle-1.10.1/src/phone_user.cpp000066400000000000000000001350771277565361200167440ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "phone_user.h" #include "log.h" #include "userintf.h" #include "util.h" #include "audits/memman.h" #include "im/im_iscomposing_body.h" #include "presence/presence_epa.h" extern t_phone *phone; extern t_event_queue *evq_timekeeper; extern t_event_queue *evq_sender; extern string user_host; extern string local_hostname; void t_phone_user::cleanup_mwi_dialog(void) { if (mwi_dialog && mwi_dialog->get_subscription_state() == SS_TERMINATED) { string reason_termination = mwi_dialog->get_reason_termination(); bool may_resubscribe = mwi_dialog->get_may_resubscribe(); unsigned long dur_resubscribe = mwi_dialog->get_resubscribe_after(); MEMMAN_DELETE(mwi_dialog); delete mwi_dialog; mwi_dialog = NULL; stun_binding_inuse_mwi = false; cleanup_stun_data(); cleanup_nat_keepalive(); if (mwi_auto_resubscribe) { if (may_resubscribe) { if (dur_resubscribe > 0) { start_resubscribe_mwi_timer(dur_resubscribe * 1000); } else { subscribe_mwi(); } } else if (reason_termination.empty()) { start_resubscribe_mwi_timer(DUR_MWI_FAILURE * 1000); } } } } void t_phone_user::cleanup_stun_data(void) { if (!use_stun) return; if (!stun_binding_inuse_registration && !stun_binding_inuse_mwi && stun_binding_inuse_presence == 0) { stun_public_ip_sip = 0; stun_public_port_sip = 0; } } void t_phone_user::cleanup_nat_keepalive(void) { if (register_ip_port.ipaddr == 0 && register_ip_port.port == 0 && !mwi_dialog) { if (id_nat_keepalive) phone->stop_timer(PTMR_NAT_KEEPALIVE, this); } } void t_phone_user::sync_nat_keepalive(void) { if (user_config->get_enable_nat_keepalive() && !id_nat_keepalive) { send_nat_keepalive(); phone->start_timer(PTMR_NAT_KEEPALIVE, this); } } void t_phone_user::cleanup_tcp_ping(void) { if (register_ip_port.ipaddr == 0 && register_ip_port.port == 0) { if (id_tcp_ping) phone->stop_timer(PTMR_TCP_PING, this); } } void t_phone_user::cleanup_registration_data(void) { register_ip_port.ipaddr = 0; register_ip_port.port = 0; stun_binding_inuse_registration = false; cleanup_stun_data(); cleanup_nat_keepalive(); cleanup_tcp_ping(); } t_phone_user::t_phone_user(const t_user &profile) { user_config = profile.copy(); service = new t_service(user_config); MEMMAN_NEW(service); buddy_list = new t_buddy_list(this); MEMMAN_NEW(buddy_list); presence_epa = new t_presence_epa(this); MEMMAN_NEW(presence_epa); string err_msg; if (buddy_list->load(err_msg)) { log_file->write_header("t_phone_user::t_phone_user"); log_file->write_raw(user_config->get_profile_name()); log_file->write_raw(": buddy list loaded.\n"); log_file->write_footer(); } else { log_file->write_header("t_phone_user::t_phone_user", LOG_NORMAL, LOG_CRITICAL); log_file->write_raw(user_config->get_profile_name()); log_file->write_raw(": falied to load buddy list.\n"); log_file->write_raw(err_msg); log_file->write_endl(); log_file->write_footer(); } active = true; r_options = NULL; r_register = NULL; r_deregister = NULL; r_query_register = NULL; r_message = NULL; r_stun = NULL; // Initialize registration data // Call-ID cannot be set here as user_host is not determined yet. register_seqnr = NEW_SEQNR; is_registered = false; register_ip_port.ipaddr = 0L; register_ip_port.port = 0; last_reg_failed = false; // Initialize STUN data stun_public_ip_sip = 0L; stun_public_port_sip = 0; stun_binding_inuse_registration = false; stun_binding_inuse_mwi = false; stun_binding_inuse_presence = 0; register_after_stun = false; mwi_subscribe_after_stun = false; presence_subscribe_after_stun = false; use_stun = false; use_nat_keepalive = false; // Timers id_registration = 0; id_nat_keepalive = 0; id_tcp_ping = 0; id_resubscribe_mwi = 0; // MWI mwi_dialog = NULL; mwi_auto_resubscribe = false; } t_phone_user::~t_phone_user() { // Stop timers if (id_registration) phone->stop_timer(PTMR_REGISTRATION, this); if (id_nat_keepalive) phone->stop_timer(PTMR_NAT_KEEPALIVE, this); if (id_tcp_ping) phone->stop_timer(PTMR_TCP_PING, this); // Delete pointers if (r_options) { MEMMAN_DELETE(r_options); delete r_options; } if (r_register) { MEMMAN_DELETE(r_register); delete r_register; } if (r_deregister) { MEMMAN_DELETE(r_deregister); delete r_deregister; } if (r_query_register) { MEMMAN_DELETE(r_query_register); delete r_query_register; } if (r_message) { MEMMAN_DELETE(r_message); delete r_message; } if (r_stun) { MEMMAN_DELETE(r_stun); delete r_stun; } for (list::iterator it = pending_messages.begin(); it != pending_messages.end(); ++it) { MEMMAN_DELETE(*it); delete *it; } if (mwi_dialog) { MEMMAN_DELETE(mwi_dialog); delete mwi_dialog; } MEMMAN_DELETE(service); delete service; MEMMAN_DELETE(presence_epa); delete presence_epa; MEMMAN_DELETE(buddy_list); delete buddy_list; buddy_list = NULL; MEMMAN_DELETE(user_config); delete user_config; } t_user *t_phone_user::get_user_profile(void) { return user_config; } t_buddy_list *t_phone_user::get_buddy_list(void) { return buddy_list; } t_presence_epa *t_phone_user::get_presence_epa(void) { return presence_epa; } void t_phone_user::registration(t_register_type register_type, bool re_register, unsigned long expires) { // If STUN is enabled, then do a STUN query before registering to // determine the public IP address. if (register_type == REG_REGISTER && use_stun) { if (stun_public_ip_sip == 0) { send_stun_request(); register_after_stun = true; registration_time = expires; return; } stun_binding_inuse_registration = true; } // Stop registration timer for non-query request if (register_type != REG_QUERY) { phone->stop_timer(PTMR_REGISTRATION, this); } // Create call-id if no call-id is created yet if (register_call_id == "") { register_call_id = NEW_CALL_ID(user_config); } // RFC 3261 10.2 // Construct REGISTER request t_request *req = create_request(REGISTER, t_url(string(USER_SCHEME) + ":" + user_config->get_domain())); // To req->hdr_to.set_uri(user_config->create_user_uri(false)); req->hdr_to.set_display(user_config->get_display(false)); //Call-ID req->hdr_call_id.set_call_id(register_call_id); // CSeq req->hdr_cseq.set_method(REGISTER); req->hdr_cseq.set_seqnr(++register_seqnr); // Contact t_contact_param contact; switch (register_type) { case REG_REGISTER: // URI contact.uri.set_url(user_config->create_user_contact(false, h_ip2str(req->get_local_ip()))); // Expires if (expires > 0) { if (user_config->get_registration_time_in_contact()) { contact.set_expires(expires); } else { req->hdr_expires.set_time(expires); } } // q-value if (user_config->get_reg_add_qvalue()) { contact.set_qvalue(user_config->get_reg_qvalue()); } req->hdr_contact.add_contact(contact); break; case REG_DEREGISTER: contact.uri.set_url(user_config->create_user_contact(false, h_ip2str(req->get_local_ip()))); if (user_config->get_registration_time_in_contact()) { contact.set_expires(0); } else { req->hdr_expires.set_time(0); } req->hdr_contact.add_contact(contact); break; case REG_DEREGISTER_ALL: req->hdr_contact.set_any(); req->hdr_expires.set_time(0); break; default: break; } // Allow SET_HDR_ALLOW(req->hdr_allow, user_config); // Store request in the proper place t_tuid tuid; switch(register_type) { case REG_REGISTER: // Delete a possible pending registration request if (r_register) { MEMMAN_DELETE(r_register); delete r_register; } r_register = new t_client_request(user_config, req, 0); MEMMAN_NEW(r_register); tuid = r_register->get_tuid(); // Store expiration time for re-registration. registration_time = expires; break; case REG_QUERY: // Delete a possible pending query registration request if (r_query_register) { MEMMAN_DELETE(r_query_register); delete r_query_register; } r_query_register = new t_client_request(user_config, req, 0); MEMMAN_NEW(r_query_register); tuid = r_query_register->get_tuid(); break; case REG_DEREGISTER: case REG_DEREGISTER_ALL: // Delete a possible pending de-registration request if (r_deregister) { MEMMAN_DELETE(r_deregister); delete r_deregister; } r_deregister = new t_client_request(user_config, req, 0); MEMMAN_NEW(r_deregister); tuid = r_deregister->get_tuid(); break; default: assert(false); } // Send REGISTER authorizor.set_re_register(re_register); ui->cb_register_inprog(user_config, register_type); phone->send_request(user_config, req, tuid); MEMMAN_DELETE(req); delete req; } void t_phone_user::options(const t_url &to_uri, const string &to_display) { // RFC 3261 11.1 // Construct OPTIONS request t_request *req = create_request(OPTIONS, to_uri); // To req->hdr_to.set_uri(to_uri); req->hdr_to.set_display(to_display); // Call-ID req->hdr_call_id.set_call_id(NEW_CALL_ID(user_config)); // CSeq req->hdr_cseq.set_method(OPTIONS); req->hdr_cseq.set_seqnr(NEW_SEQNR); // Accept req->hdr_accept.add_media(t_media("application","sdp")); // Store and send request // Delete a possible pending options request if (r_options) { MEMMAN_DELETE(r_options); delete r_options; } r_options = new t_client_request(user_config, req, 0); MEMMAN_NEW(r_options); phone->send_request(user_config, req, r_options->get_tuid()); MEMMAN_DELETE(req); delete req; } void t_phone_user::handle_response_out_of_dialog(t_response *r, t_tuid tuid, t_tid tid) { t_client_request **current_cr; t_request *req; bool is_register = false; t_buddy *buddy; if (r_register && r_register->get_tuid() == tuid) { current_cr = &r_register; is_register = true; } else if (r_deregister && r_deregister->get_tuid() == tuid) { current_cr = &r_deregister; is_register = true; } else if (r_query_register && r_query_register->get_tuid() == tuid) { current_cr = &r_query_register; is_register = true; } else if (r_options && r_options->get_tuid() == tuid) { current_cr = &r_options; } else if (r_message && r_message->get_tuid() == tuid) { current_cr = &r_message; } else if (mwi_dialog && mwi_dialog->match_response(r, tuid)) { mwi_dialog->recvd_response(r, tuid, tid); cleanup_mwi_dialog(); return; } else if (presence_epa && presence_epa->match_response(r, tuid)) { presence_epa->recv_response(r, tuid, tid); return; } else if (buddy_list->match_response(r, tuid, &buddy)) { buddy->recvd_response(r, tuid, tid); if (buddy->must_delete_now()) buddy_list->del_buddy(*buddy); return; } else { // Response does not match any pending request. log_file->write_report("Response does not match any pending request.", "t_phone_user::handle_response_out_of_dialog", LOG_NORMAL, LOG_WARNING); return; } req = (*current_cr)->get_request(); // Authentication if (r->must_authenticate()) { if (authorize(req, r)) { resend_request(req, is_register, *current_cr); return; } // Authentication failed // Handle the 401/407 as a normal failure response } // RFC 3263 4.3 // Failover if (r->code == R_503_SERVICE_UNAVAILABLE) { if (req->next_destination()) { log_file->write_report("Failover to next destination.", "t_phone_user::handle_response_out_of_dialog"); resend_request(req, is_register, *current_cr); return; } } // Redirect failed request if there is another destination if (r->get_class() > R_2XX && user_config->get_allow_redirection()) { // If the response is a 3XX response then add redirection // contacts if (r->get_class() == R_3XX && r->hdr_contact.is_populated()) { (*current_cr)->redirector.add_contacts( r->hdr_contact.contact_list); } // Get next destination t_contact_param contact; if ((*current_cr)->redirector.get_next_contact(contact)) { // Ask user for permission to redirect if indicated // by user config bool permission = true; if (user_config->get_ask_user_to_redirect()) { permission = ui->cb_ask_user_to_redirect_request( user_config, contact.uri, contact.display, r->hdr_cseq.method); } if (permission) { req->uri = contact.uri; req->calc_destinations(*user_config); ui->cb_redirecting_request(user_config, contact); resend_request(req, is_register, *current_cr); return; } } } // REGISTER (register) if (r_register && r_register->get_tuid() == tuid) { bool re_register; handle_response_register(r, re_register); MEMMAN_DELETE(r_register); delete r_register; r_register = NULL; if (re_register) registration(REG_REGISTER, authorizor.get_re_register(), registration_time); return; } // REGISTER (de-register) if (r_deregister && r_deregister->get_tuid() == tuid) { handle_response_deregister(r); MEMMAN_DELETE(r_deregister); delete r_deregister; r_deregister = NULL; return; } // REGISTER (query) if (r_query_register && r_query_register->get_tuid() == tuid) { handle_response_query_register(r); MEMMAN_DELETE(r_query_register); delete r_query_register; r_query_register = NULL; return; } // OPTIONS if (r_options && r_options->get_tuid() == tuid) { handle_response_options(r); MEMMAN_DELETE(r_options); delete r_options; r_options = NULL; return; } // MESSAGE if (r_message && r_message->get_tuid() == tuid) { handle_response_message(r); MEMMAN_DELETE(r_message); delete r_message; r_message = NULL; // Send next pending MESSAGE if (!pending_messages.empty()) { t_request *req = pending_messages.front(); pending_messages.pop_front(); r_message = new t_client_request(user_config, req, 0); MEMMAN_NEW(r_message); phone->send_request(user_config, req, r_message->get_tuid()); MEMMAN_DELETE(req); delete req; } return; } // Response does not match any pending request. Do nothing. } void t_phone_user::resend_request(t_request *req, bool is_register, t_client_request *cr) { // A new sequence number must be assigned if (is_register) { req->hdr_cseq.set_seqnr(++register_seqnr); } else { req->hdr_cseq.seqnr++; } // Create a new via-header. Otherwise the // request will be seen as a retransmission unsigned long local_ip = req->get_local_ip(); req->hdr_via.via_list.clear(); t_via via(USER_HOST(user_config, h_ip2str(local_ip)), PUBLIC_SIP_PORT(user_config)); req->hdr_via.add_via(via); cr->renew(0); phone->send_request(user_config, req, cr->get_tuid()); } void t_phone_user::handle_response_out_of_dialog(StunMessage *r, t_tuid tuid) { if (!r_stun || r_stun->get_tuid() != tuid) { // Response does not match pending STUN request return; } if (r->msgHdr.msgType == BindResponseMsg && r->hasMappedAddress) { // The STUN response contains the public IP. stun_public_ip_sip = r->mappedAddress.ipv4.addr; stun_public_port_sip = r->mappedAddress.ipv4.port; MEMMAN_DELETE(r_stun); delete r_stun; r_stun = NULL; if (register_after_stun) { register_after_stun = false; registration(REG_REGISTER, false, registration_time); } if (mwi_subscribe_after_stun) { mwi_subscribe_after_stun = false; subscribe_mwi(); } if (presence_subscribe_after_stun) { presence_subscribe_after_stun = false; buddy_list->stun_completed(); } return; } if (r->msgHdr.msgType == BindErrorResponseMsg && r->hasErrorCode) { // STUN request failed. ui->cb_stun_failed(user_config, r->errorCode.errorClass * 100 + r->errorCode.number, r->errorCode.reason); } else { // No satisfying STUN response was received. ui->cb_stun_failed(user_config); } MEMMAN_DELETE(r_stun); delete r_stun; r_stun = NULL; if (register_after_stun) { // Retry registration later. bool first_failure = !last_reg_failed; last_reg_failed = true; is_registered = false; ui->cb_register_stun_failed(user_config, first_failure); phone->start_set_timer(PTMR_REGISTRATION, DUR_REG_FAILURE * 1000, this); register_after_stun = false; } if (mwi_subscribe_after_stun) { // Retry MWI subscription later start_resubscribe_mwi_timer(DUR_MWI_FAILURE * 1000); mwi_subscribe_after_stun = false; } if (presence_subscribe_after_stun) { buddy_list->stun_failed(); presence_subscribe_after_stun = false; } } void t_phone_user::handle_response_register(t_response *r, bool &re_register) { t_contact_param *c; unsigned long expires; unsigned long e; bool first_failure, first_success; re_register = false; // Store the destination IP address/port of the REGISTER message. // To this destination the NAT keep alive packets will be sent. t_request *req = r_register->get_request(); req->get_destination(register_ip_port, *user_config); switch(r->get_class()) { case R_2XX: last_reg_failed = false; // Stop registration timer if one was running phone->stop_timer(PTMR_REGISTRATION, this); c = r->hdr_contact.find_contact(req->hdr_contact.contact_list.front().uri); if (!c) { if (!user_config->get_allow_missing_contact_reg()) { is_registered = false; log_file->write_report( "Contact header is missing.", "t_phone_user::handle_response_register", LOG_NORMAL, LOG_WARNING); ui->cb_invalid_reg_resp(user_config, r, "Contact header missing."); cleanup_registration_data(); return; } else { log_file->write_report( "Cannot find matching contact header.", "t_phone_user::handle_response_register", LOG_NORMAL, LOG_DEBUG); } } if (c && c->is_expires_present() && c->get_expires() != 0) { expires = c->get_expires(); } else if (r->hdr_expires.is_populated() && r->hdr_expires.time != 0) { expires = r->hdr_expires.time; } else { if (!user_config->get_allow_missing_contact_reg()) { is_registered = false; log_file->write_report( "Expires parameter/header mising.", "t_phone_user::handle_response_register", LOG_NORMAL, LOG_WARNING); ui->cb_invalid_reg_resp(user_config, r, "Expires parameter/header mising."); cleanup_registration_data(); return; } expires = user_config->get_registration_time(); // Assume a default expiration of 3600 sec if no expiry // time was returned. if (expires == 0) expires = 3600; } // Start new registration timer // The maximum value of the timer can be 2^32-1 s // The maximum timer that we can handle however is 2^31-1 ms e = (expires > 2147483 ? 2147483 : expires); phone->start_set_timer(PTMR_REGISTRATION, e * 1000, this); // Save the Service-Route if present the response contains any // RFC 3608 6 // Collect the service route to route later initial requests. if (r->hdr_service_route.is_populated()) { service_route = r->hdr_service_route.route_list; log_file->write_header("t_phone_user::handle_response_register"); log_file->write_raw("Store service route:\n"); for (list::const_iterator it = service_route.begin(); it != service_route.end(); ++it) { log_file->write_raw(it->encode()); log_file->write_endl(); } log_file->write_footer(); } else { if (!service_route.empty()) { log_file->write_report("Clear service route.", "t_phone_user::handle_response_register"); service_route.clear(); } } first_success = !is_registered; is_registered = true; ui->cb_register_success(user_config, r, expires, first_success); // Start sending NAT keepalive packets when STUN is used // (or in case of symmetric firewall) if (use_nat_keepalive && id_nat_keepalive == 0) { // Just start the NAT keepalive timer. The REGISTER // message itself created the NAT binding. So there is // no need to send a NAT keep alive packet now. phone->start_timer(PTMR_NAT_KEEPALIVE, this); } // Start sending TCP ping packets on persistent TCP connections. if (user_config->get_persistent_tcp() && id_tcp_ping == 0) { phone->start_timer(PTMR_TCP_PING, this); } // Registration succeeded. If sollicited MWI is provisioned // and no MWI subscription is established yet, then subscribe // to MWI. if (user_config->get_mwi_sollicited() && !mwi_auto_resubscribe) { subscribe_mwi(); } // Publish presence state if not yet published. if (user_config->get_pres_publish_startup() && presence_epa->get_epa_state() == t_epa::EPA_UNPUBLISHED) { publish_presence(t_presence_state::ST_BASIC_OPEN); } // Subscribe to buddy list presence if not done so. if (!buddy_list->get_is_subscribed()) { subscribe_presence(); } break; case R_4XX: is_registered = false; // RFC 3261 10.3 if (r->code == R_423_INTERVAL_TOO_BRIEF) { if (!r->hdr_min_expires.is_populated()) { // Violation of RFC 3261 10.3 item 7 log_file->write_report("Min-Expires header missing from 423 response.", "t_phone_user::handle_response_register", LOG_NORMAL, LOG_WARNING); ui->cb_invalid_reg_resp(user_config, r, "Min-Expires header missing."); cleanup_registration_data(); return; } if (r->hdr_min_expires.time <= registration_time) { // Wrong Min-Expires time string s = "Min-Expires ("; s += ulong2str(r->hdr_min_expires.time); s += ") is smaller than the requested "; s += "time ("; s += ulong2str(registration_time); s += ")"; log_file->write_report(s, "t_phone_user::handle_response_register", LOG_NORMAL, LOG_WARNING); ui->cb_invalid_reg_resp(user_config, r, s); cleanup_registration_data(); return; } // Automatic re-register with Min-Expires time registration_time = r->hdr_min_expires.time; re_register = true; // No need to cleanup STUN data as a new REGISTER will be // sent immediately. return; } // If authorization failed, then do not start the continuous // re-attempts. When authorization fails the user is asked // for credentials (in GUI). So the user cancelled these // questions and should not be bothered with the same question // again every 30 seconds. The user does not have the // credentials. if (r->code == R_401_UNAUTHORIZED || r->code == R_407_PROXY_AUTH_REQUIRED) { last_reg_failed = true; ui->cb_register_failed(user_config, r, true); cleanup_registration_data(); return; } // fall thru default: first_failure = !last_reg_failed; last_reg_failed = true; is_registered = false; authorizor.remove_from_cache(""); // Clear credentials cache ui->cb_register_failed(user_config, r, first_failure); phone->start_set_timer(PTMR_REGISTRATION, DUR_REG_FAILURE * 1000, this); cleanup_registration_data(); } } void t_phone_user::handle_response_deregister(t_response *r) { is_registered = false; last_reg_failed = false; if (r->is_success()) { ui->cb_deregister_success(user_config, r); } else { ui->cb_deregister_failed(user_config, r); } cleanup_registration_data(); } void t_phone_user::handle_response_query_register(t_response *r) { if (r->is_success()) { ui->cb_fetch_reg_result(user_config, r); } else { ui->cb_fetch_reg_failed(user_config, r); } } void t_phone_user::handle_response_options(t_response *r) { ui->cb_options_response(r); } void t_phone_user::handle_response_message(t_response *r) { t_request *req = r_message->get_request(); if (req->body && req->body->get_type() == BODY_IM_ISCOMPOSING_XML) { // Response on message composing indication. if (r->code == R_415_UNSUPPORTED_MEDIA_TYPE) { // RFC 3994 4 // In SIP-based IM, The composer MUST cease transmitting // status messages if the receiver returned a 415 status // code (Unsupported Media Type) in response to MESSAGE // request containing the status indication. ui->cb_im_iscomposing_not_supported(user_config, r); } } else { // Response on instant message ui->cb_message_response(user_config, r, req); } } void t_phone_user::subscribe_mwi(void) { mwi_auto_resubscribe = true; if (mwi_dialog) { // This situation may occur, when an unsubscription is still // in progress. The subscibe will be retried after the unsubscription // is finished. Note that mwi_auto_resubscribe has been set to true // to trigger an automatic subscription. log_file->write_header("t_phone_user::subscribe_mwi", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("MWI dialog already exists.\n"); log_file->write_raw("Subscription state: "); log_file->write_raw(t_subscription_state2str(mwi_dialog->get_subscription_state())); log_file->write_endl(); log_file->write_footer(); return; } // If STUN is enabled, then do a STUN query before registering to // determine the public IP address. if (use_stun) { if (stun_public_ip_sip == 0) { send_stun_request(); mwi_subscribe_after_stun = true; return; } stun_binding_inuse_mwi = true; } mwi_dialog = new t_mwi_dialog(this); MEMMAN_NEW(mwi_dialog); // RFC 3842 4.1 // The example flow shows: // Request-URI = mail_user@mailbox_server // To = user@domain mwi_dialog->subscribe(DUR_MWI(user_config), user_config->get_mwi_uri(), user_config->create_user_uri(false), user_config->get_display(false)); // Start sending NAT keepalive packets when STUN is used // (or in case of symmetric firewall) if (use_nat_keepalive && id_nat_keepalive == 0) { // Just start the NAT keepalive timer. The SUBSCRIBE // message will create the NAT binding. So there is // no need to send a NAT keep alive packet now. phone->start_timer(PTMR_NAT_KEEPALIVE, this); } cleanup_mwi_dialog(); } void t_phone_user::unsubscribe_mwi(void) { mwi_auto_resubscribe = false; stop_resubscribe_mwi_timer(); mwi.set_status(t_mwi::MWI_UNKNOWN); if (mwi_dialog) { mwi_dialog->unsubscribe(); cleanup_mwi_dialog(); } ui->cb_update_mwi(); } bool t_phone_user::is_mwi_subscribed(void) const { if (mwi_dialog) { return mwi_dialog->get_subscription_state() == SS_ESTABLISHED; } return false; } bool t_phone_user::is_mwi_terminated(void) const { return mwi_dialog == NULL; } void t_phone_user::handle_mwi_unsollicited(t_request *r, t_tid tid) { if (user_config->get_mwi_sollicited()) { // Unsollicited MWI is not supported t_response *resp = r->create_response(R_403_FORBIDDEN); phone->send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return; } if (r->body && r->body->get_type() == BODY_SIMPLE_MSG_SUM) { t_simple_msg_sum_body *body = dynamic_cast(r->body); mwi.set_msg_waiting(body->get_msg_waiting()); t_msg_summary summary; if (body->get_msg_summary(MSG_CONTEXT_VOICE, summary)) { mwi.set_voice_msg_summary(summary); } mwi.set_status(t_mwi::MWI_KNOWN); } t_response *resp = r->create_response(R_200_OK); phone->send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; ui->cb_update_mwi(); } void t_phone_user::subscribe_presence(void) { assert(buddy_list); buddy_list->subscribe_presence(); } void t_phone_user::unsubscribe_presence(void) { assert(buddy_list); buddy_list->unsubscribe_presence(); } void t_phone_user::publish_presence(t_presence_state::t_basic_state basic_state) { assert(presence_epa); presence_epa->publish_presence(basic_state); } void t_phone_user::unpublish_presence(void) { assert(presence_epa); presence_epa->unpublish(); } bool t_phone_user::is_presence_terminated(void) const { assert(buddy_list); return buddy_list->is_presence_terminated(); } bool t_phone_user::send_message(const t_url &to_uri, const string &to_display, const t_msg &msg) { t_request *req = create_request(MESSAGE, to_uri); // To req->hdr_to.set_uri(to_uri); req->hdr_to.set_display(to_display); // Call-ID req->hdr_call_id.set_call_id(NEW_CALL_ID(user_config)); // CSeq req->hdr_cseq.set_method(MESSAGE); req->hdr_cseq.set_seqnr(NEW_SEQNR); // Subject if (!msg.subject.empty()) { req->hdr_subject.set_subject(msg.subject); } // Body and Content-Type if (!msg.has_attachment) { // A message without an attachment is a text message. req->set_body_plain_text(msg.message, MSG_TEXT_CHARSET); } else { // Send message with file attachment if (!req->set_body_from_file(msg.attachment_filename, msg.attachment_media)) { log_file->write_header("t_phone_user::send_message", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Could not read file "); log_file->write_raw(msg.attachment_filename); log_file->write_endl(); log_file->write_footer(); MEMMAN_DELETE(req); delete req; return false; } // Content-Disposition req->hdr_content_disp.set_type(DISPOSITION_ATTACHMENT); req->hdr_content_disp.set_filename(msg.attachment_save_as_name); } // Store and send request // Delete a possible pending options request if (r_message) { // RFC 3428 8 // Send only 1 message at a time. // Store the message. It will be sent if the previous // message transaction is finished. pending_messages.push_back(req); } else { r_message = new t_client_request(user_config, req, 0); MEMMAN_NEW(r_message); phone->send_request(user_config, req, r_message->get_tuid()); MEMMAN_DELETE(req); delete req; } return true; } bool t_phone_user::send_im_iscomposing(const t_url &to_uri, const string &to_display, const string &state, time_t refresh) { t_request *req = create_request(MESSAGE, to_uri); // To req->hdr_to.set_uri(to_uri); req->hdr_to.set_display(to_display); // Call-ID req->hdr_call_id.set_call_id(NEW_CALL_ID(user_config)); // CSeq req->hdr_cseq.set_method(MESSAGE); req->hdr_cseq.set_seqnr(NEW_SEQNR); // Body and Content-Type t_im_iscomposing_xml_body *body = new t_im_iscomposing_xml_body; MEMMAN_NEW(body); body->set_state(state); body->set_refresh(refresh); req->body = body; req->hdr_content_type.set_media(body->get_media()); // Store and send request // Delete a possible pending options request if (r_message) { // RFC 3428 8 // Send only 1 message at a time. // Store the message. It will be sent if the previous // message transaction is finished. pending_messages.push_back(req); } else { r_message = new t_client_request(user_config, req, 0); MEMMAN_NEW(r_message); phone->send_request(user_config, req, r_message->get_tuid()); MEMMAN_DELETE(req); delete req; } return true; } void t_phone_user::recvd_message(t_request *r, t_tid tid) { t_response *resp; if (!r->body || !MESSAGE_CONTENT_TYPE_SUPPORTED(*r)) { resp = r->create_response(R_415_UNSUPPORTED_MEDIA_TYPE); // RFC 3261 21.4.13 SET_MESSAGE_HDR_ACCEPT(resp->hdr_accept); phone->send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return; } if (r->body && r->body->get_type() == BODY_IM_ISCOMPOSING_XML) { // Message composing indication t_im_iscomposing_xml_body *sb = dynamic_cast(r->body); im::t_composing_state state = im::string2composing_state(sb->get_state()); time_t refresh = sb->get_refresh(); ui->cb_im_iscomposing_request(user_config, r, state, refresh); resp = r->create_response(R_200_OK); } else { bool accepted = ui->cb_message_request(user_config, r); if (accepted) { resp = r->create_response(R_200_OK); } else { if (user_config->get_im_max_sessions() == 0) { resp = r->create_response(R_603_DECLINE); } else { resp = r->create_response(R_486_BUSY_HERE); } } } phone->send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; } void t_phone_user::recvd_notify(t_request *r, t_tid tid) { bool partial_match = false; if (r->hdr_to.tag.empty()) { // Unsollicited NOTIFY handle_mwi_unsollicited(r, tid); return; } if (mwi_dialog && mwi_dialog->match_request(r, partial_match)) { // Sollicited NOTIFY mwi_dialog->recvd_request(r, 0, tid); cleanup_mwi_dialog(); return; } // A NOTIFY may be received before a 2XX on SUBSCRIBE. // In this case the NOTIFY will establish the dialog. if (partial_match && mwi_dialog->get_remote_tag().empty()) { mwi_dialog->recvd_request(r, 0, tid); cleanup_mwi_dialog(); return; } t_buddy *buddy; if (buddy_list->match_request(r, &buddy)) { buddy->recvd_request(r, 0, tid); if (buddy->must_delete_now()) buddy_list->del_buddy(*buddy); return; } // RFC 3265 4.4.9 // A SUBSCRIBE request may have forked. So multiple NOTIFY's // can be received. Twinkle simply rejects additional NOTIFY's with // a 481. This should terminate the forked dialog, such that only // one dialog will remain. t_response *resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); phone->send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; } void t_phone_user::send_stun_request(void) { if (r_stun) { log_file->write_report("STUN request already in progress.", "t_phone_user::send_stun_request", LOG_NORMAL, LOG_DEBUG); return; } StunMessage req; StunAtrString username; username.sizeValue = 0; stunBuildReqSimple(&req, username, false, false); r_stun = new t_client_request(user_config, &req, 0); MEMMAN_NEW(r_stun); phone->send_request(user_config, &req, r_stun->get_tuid()); return; } // NOTE: The term "NAT keep alive" does not cover all uses. The keep alives will // also be sent when there is a symmetric firewall without NAT. void t_phone_user::send_nat_keepalive(void) { // Send keep-alive to registrar/proxy if (register_ip_port.ipaddr != 0 && register_ip_port.port != 0 && register_ip_port.transport == "udp") { evq_sender->push_nat_keepalive(register_ip_port.ipaddr, register_ip_port.port); } // Send keep-alive to MWI mailbox if different from registrar/proxy if (mwi_dialog) { t_ip_port mwi_ip_port = mwi_dialog->get_remote_ip_port(); if (!mwi_ip_port.is_null() && mwi_ip_port != register_ip_port && mwi_ip_port.transport == "udp") { evq_sender->push_nat_keepalive(mwi_ip_port.ipaddr, mwi_ip_port.port); } } } void t_phone_user::send_tcp_ping(void) { if (register_ip_port.ipaddr != 0 && register_ip_port.port != 0 && register_ip_port.transport == "tcp") { evq_sender->push_tcp_ping(user_config->create_user_uri(false), register_ip_port.ipaddr, register_ip_port.port); } } void t_phone_user::timeout(t_phone_timer timer) { switch (timer) { case PTMR_REGISTRATION: id_registration = 0; // Registration expired. Re-register. if (is_registered || last_reg_failed) { // Re-register if no register is pending if (!r_register) { registration(REG_REGISTER, true, registration_time); } } break; case PTMR_NAT_KEEPALIVE: id_nat_keepalive = 0; // Send a new NAT keepalive packet if (use_nat_keepalive) { send_nat_keepalive(); phone->start_timer(PTMR_NAT_KEEPALIVE, this); } break; case PTMR_TCP_PING: id_tcp_ping = 0; // Send a TCP ping; if (user_config->get_persistent_tcp()) { send_tcp_ping(); phone->start_timer(PTMR_TCP_PING, this); } break; default: assert(false); } } void t_phone_user::timeout_sub(t_subscribe_timer timer, t_object_id id_timer) { t_buddy *buddy; switch (timer) { case STMR_SUBSCRIPTION: if (mwi_dialog && mwi_dialog->match_timer(timer, id_timer)) { mwi_dialog->timeout(timer); } else if (buddy_list->match_timer(timer, id_timer, &buddy)) { buddy->timeout(timer, id_timer); if (buddy->must_delete_now()) buddy_list->del_buddy(*buddy); } else if (id_timer == id_resubscribe_mwi) { // Try to subscribe to MWI id_resubscribe_mwi = 0; subscribe_mwi(); } break; default: assert(false); } } void t_phone_user::timeout_publish(t_publish_timer timer, t_object_id id_timer) { switch (timer) { case PUBLISH_TMR_PUBLICATION: if (presence_epa->match_timer(timer, id_timer)) { presence_epa->timeout(timer); } break; default: assert(false); } } void t_phone_user::handle_broken_connection(void) { log_file->write_header("t_phone_user::handle_broken_connection", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Handle broken connection for "); log_file->write_raw(user_config->get_profile_name()); log_file->write_endl(); log_file->write_footer(); // A persistent connection has been broken. The connection must be re-established // by registering again. This is only needed when the user was registered already. // If no registration was present, then the persistent connection should not have // been established. if (is_registered) { // Re-register if no register is pending if (!r_register) { log_file->write_header("t_phone_user::handle_broken_connection", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Re-establish broken connection for "); log_file->write_raw(user_config->get_profile_name()); log_file->write_endl(); log_file->write_footer(); registration(REG_REGISTER, true, registration_time); } } } bool t_phone_user::match_subscribe_timer(t_subscribe_timer timer, t_object_id id_timer) const { t_buddy *buddy; if (mwi_dialog && mwi_dialog->match_timer(timer, id_timer)) { return true; } t_phone_user *self = const_cast(this); if (self->buddy_list->match_timer(timer, id_timer, &buddy)) { return true; } return id_timer == id_resubscribe_mwi; } bool t_phone_user::match_publish_timer(t_publish_timer timer, t_object_id id_timer) const { assert(presence_epa); return presence_epa->match_timer(timer, id_timer); } void t_phone_user::start_resubscribe_mwi_timer(unsigned long duration) { t_tmr_subscribe *t; t = new t_tmr_subscribe(duration, STMR_SUBSCRIPTION, 0, 0, SIP_EVENT_MSG_SUMMARY, ""); MEMMAN_NEW(t); id_resubscribe_mwi = t->get_object_id(); evq_timekeeper->push_start_timer(t); MEMMAN_DELETE(t); delete t; } void t_phone_user::stop_resubscribe_mwi_timer(void) { if (id_resubscribe_mwi != 0) { evq_timekeeper->push_stop_timer(id_resubscribe_mwi); id_resubscribe_mwi = 0; } } t_request *t_phone_user::create_request(t_method m, const t_url &request_uri) const { t_request *req = new t_request(m); MEMMAN_NEW(req); // From req->hdr_from.set_uri(user_config->create_user_uri(false)); req->hdr_from.set_display(user_config->get_display(false)); req->hdr_from.set_tag(NEW_TAG); // Max-Forwards header (mandatory) req->hdr_max_forwards.set_max_forwards(MAX_FORWARDS); // User-Agent SET_HDR_USER_AGENT(req->hdr_user_agent); // Set request URI and calculate destinations. By calculating // destinations now, the request can be resend to a next destination // if failover is needed. if (m == REGISTER) { // For a REGISTER do not use the service route for routing. req->uri = request_uri; } else { // RFC 3608 // For all other requests, use the service route set for routing. req->set_route(request_uri, service_route); } req->calc_destinations(*user_config); // The Via header can only be created after the destinations // are calculated, because the destination deterimines which // local IP address should be used. // Via unsigned long local_ip = req->get_local_ip(); t_via via(USER_HOST(user_config, h_ip2str(local_ip)), PUBLIC_SIP_PORT(user_config)); req->hdr_via.add_via(via); return req; } t_response *t_phone_user::create_options_response(t_request *r, bool in_dialog) const { t_response *resp; // RFC 3261 11.2 switch(phone->get_state()) { case PS_IDLE: if (!in_dialog && service->is_dnd_active()) { resp = r->create_response(R_480_TEMP_NOT_AVAILABLE); } else { resp = r->create_response(R_200_OK); } break; case PS_BUSY: if (in_dialog) { resp = r->create_response(R_200_OK); } else { resp = r->create_response(R_486_BUSY_HERE); } break; default: assert(false); } SET_HDR_ALLOW(resp->hdr_allow, user_config); SET_HDR_ACCEPT(resp->hdr_accept); SET_HDR_ACCEPT_ENCODING(resp->hdr_accept_encoding); SET_HDR_ACCEPT_LANGUAGE(resp->hdr_accept_language); SET_HDR_SUPPORTED(resp->hdr_supported, user_config); if (user_config->get_ext_100rel() != EXT_DISABLED) { resp->hdr_supported.add_feature(EXT_100REL); } // TODO: include SDP body if requested (optional) return resp; } bool t_phone_user::get_is_registered(void) const { return is_registered; } bool t_phone_user::get_last_reg_failed(void) const { return last_reg_failed; } string t_phone_user::get_ip_sip(const string &auto_ip) const { if (stun_public_ip_sip) return h_ip2str(stun_public_ip_sip); if (user_config->get_use_nat_public_ip()) return user_config->get_nat_public_ip(); if (LOCAL_IP == AUTO_IP4_ADDRESS) return auto_ip; return LOCAL_IP; } unsigned short t_phone_user::get_public_port_sip(void) const { if (stun_public_port_sip) return stun_public_port_sip; return sys_config->get_sip_port(); } list t_phone_user::get_service_route(void) const { return service_route; } bool t_phone_user::match(t_response *r, t_tuid tuid) const { t_buddy *dummy; if (r_register && r_register->get_tuid() == tuid) { return true; } else if (r_deregister && r_deregister->get_tuid() == tuid) { return true; } else if (r_query_register && r_query_register->get_tuid() == tuid) { return true; } else if (r_options && r_options->get_tuid() == tuid) { return true; } else if (r_message && r_message->get_tuid() == tuid) { return true; } else if (mwi_dialog && mwi_dialog->match_response(r, tuid)) { return true; } else if (presence_epa && presence_epa->match_response(r, tuid)) { return true; } else if (buddy_list && buddy_list->match_response(r, tuid, &dummy)) { return true; } else { // Response does not match any pending request. return false; } } bool t_phone_user::match(t_request *r) const { if (!r->hdr_to.tag.empty()) { // Match in-dialog requests if (mwi_dialog) { bool partial_match = false; if (mwi_dialog->match_request(r, partial_match)) return true; if (partial_match) return true; } else if (buddy_list) { t_buddy *dummy; if (buddy_list->match_request(r, &dummy)) return true; } else { return false; } } // Match on contact URI // NOTE: the host-part is not matched with the IP address to avoid // NAT traversal problems. Some providers, using hosted NAT // traversal, send an INVITE to username@. Twinkle // only knows the in this case though. This is a // fault on the provider side. if (r->uri.get_user() == user_config->get_contact_name()) { return true; } // Match on user URI if (r->uri.get_user() == user_config->get_name() && r->uri.get_host() == user_config->get_domain()) { return true; } return false; } bool t_phone_user::match(StunMessage *r, t_tuid tuid) const { if (r_stun && r_stun->get_tuid() == tuid) return true; return false; } bool t_phone_user::authorize(t_request *r, t_response *resp) { if (authorizor.authorize(user_config, r, resp)) { return true; } return false; } void t_phone_user::resend_request(t_request *req, t_client_request *cr) { return resend_request(req, false, cr); } void t_phone_user::remove_cached_credentials(const string &realm) { authorizor.remove_from_cache(realm); } bool t_phone_user::is_active(void) const { return active; } void t_phone_user::activate(const t_user &user) { // Replace old user config with the new passed user config, because // the user config might have been edited while this phone user was // inactive. delete user_config; MEMMAN_DELETE(user_config); user_config = user.copy(); // Initialize registration data register_seqnr = NEW_SEQNR; is_registered = false; register_ip_port.ipaddr = 0L; register_ip_port.port = 0; last_reg_failed = false; // Initialize STUN data stun_public_ip_sip = 0L; stun_public_port_sip = 0; use_stun = false; use_nat_keepalive = false; active = true; } void t_phone_user::deactivate(void) { // Stop timers if (id_registration) phone->stop_timer(PTMR_REGISTRATION, this); if (id_nat_keepalive) phone->stop_timer(PTMR_NAT_KEEPALIVE, this); if (id_tcp_ping) phone->stop_timer(PTMR_TCP_PING, this); if (id_resubscribe_mwi) stop_resubscribe_mwi_timer(); // Clear MWI if (mwi_dialog) { MEMMAN_DELETE(mwi_dialog); delete mwi_dialog; mwi_dialog = NULL; } mwi.set_status(t_mwi::MWI_UNKNOWN); mwi_auto_resubscribe = false; // Clear presence state // presence_epa->clear(); buddy_list->clear_presence(); // Clear STUN stun_binding_inuse_registration = false; stun_binding_inuse_mwi = false; stun_binding_inuse_presence = 0; cleanup_registration_data(); cleanup_stun_data(); active = false; } twinkle-1.10.1/src/phone_user.h000066400000000000000000000346501277565361200164040ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // State of active phone user #ifndef _PHONE_USER_H #define _PHONE_USER_H #include #include #include "auth.h" #include "protocol.h" #include "service.h" #include "transaction_layer.h" #include "user.h" #include "im/msg_session.h" #include "mwi/mwi.h" #include "mwi/mwi_dialog.h" #include "parser/request.h" #include "parser/response.h" #include "stun/stun.h" #include "presence/buddy.h" using namespace std; using namespace im; // Forward declarations class t_client_request; class t_presence_epa; class t_phone_user { private: t_user *user_config; // State // Indicates that this user is active. A non-active user // is not removed from the user list as some transactions may // still need the user info after the user got de-activated. bool active; // Requests outside a dialog t_client_request *r_options; t_client_request *r_register; t_client_request *r_deregister; t_client_request *r_query_register; t_client_request *r_message; // STUN request t_client_request *r_stun; /** * Pending MESSAGE requests. * Twinkle will only send one message at a time per user. * This satisfies the requirements in RFC 3428 8. * NOTE: as an optimization a message queue per destination * could be kept. This way a pending message for one destination * will not block a message for another destination. */ list pending_messages; /** @name Registration data */ //@{ string register_call_id; /**< Call-ID for REGISTER requests. */ unsigned long register_seqnr; /**< Last seqnr issued. */ bool is_registered; /**< Indicates if user is registered. */ unsigned long registration_time; /**< Expiration in seconds */ bool last_reg_failed; /** Indicates if last registration failed. */ /** Destination of last REGISTER */ t_ip_port register_ip_port; /** Service Route, collected from REGISTER responses */ list service_route; //@} // A STUN request can be triggered by the following events: // // * Registration // * MWI subscription. // // These events should take place after the STUN transaction has // finished. The following indicators indicate which events should // take place. bool register_after_stun; bool mwi_subscribe_after_stun; bool stun_binding_inuse_registration; bool stun_binding_inuse_mwi; // Authorizor t_auth authorizor; // MWI dialog t_mwi_dialog *mwi_dialog; /** * Indicates if MWI must be automatically resubscribed to, if the * subscription terminates with a reason telling that resubscription * is possible. */ bool mwi_auto_resubscribe; /** Buddy list for presence susbcriptions. */ t_buddy_list *buddy_list; /** Event publication agent for presence */ t_presence_epa *presence_epa; /** * Resend the request: a new sequence number will be assigned and a new via * header created (new transaction). * @param req [in] The request to resend. * @param is_register [in] indicates if this request is a register * @param cr [in] is the current client request wrapper for this request. * @note In case of a REGISTER, the internal register seqnr will be increased. */ void resend_request(t_request *req, bool is_register, t_client_request *cr); /** @name Handle responses. */ //@{ /** * Process a repsonse on a registration request. * @param r [in] The response. * @param re_register [out] Indicates if an automatic re-registration needs * to be done. */ void handle_response_register(t_response *r, bool &re_register); /** * Process a response on a de-registration request. * @param r [in] The response. */ void handle_response_deregister(t_response *r); /** * Process a response on a registration query request. * @param r [in] The response. */ void handle_response_query_register(t_response *r); /** * Process a response on an OPTIONS request. * @param r [in] The response. */ void handle_response_options(t_response *r); /** * Process a response on a MESSAGE request. * @param r [in] The response. */ void handle_response_message(t_response *r); //@} /** Send a NAT keep alive packet. */ void send_nat_keepalive(void); /** Send a TCP ping packet. */ void send_tcp_ping(void); /** Handle MWI dialog termination. */ void cleanup_mwi_dialog(void); /** Cleanup registration data for STUN and NAT keep alive. */ void cleanup_registration_data(void); public: /** @name Timers */ //@{ unsigned short id_registration; /**< Registration timeout */ unsigned short id_nat_keepalive; /**< NAT keepalive interval */ unsigned short id_tcp_ping; /**< TCP ping timer */ unsigned short id_resubscribe_mwi; /**< MWI re-subscribe after failure */ //@} /** Supplementary services */ t_service *service; /** Message Waiting Indication data. */ t_mwi mwi; /** @name STUN data */ //@{ bool use_stun; /**< Indicates if STUN must be used. */ bool use_nat_keepalive; /**< Send NAT keepalive ? */ unsigned long stun_public_ip_sip; /**< Public IP for SIP */ unsigned short stun_public_port_sip; /**< Public port for SIP */ /** Number of presence subscriptions using the STUN binding. */ unsigned short stun_binding_inuse_presence; /**< Subscribe to presence after STUN transaction completed. */ bool presence_subscribe_after_stun; //@} /** * The constructor will create a copy of profile. * @param profile [in] User profile of this phone user. */ t_phone_user(const t_user &profile); /** Destructor. */ ~t_phone_user(); /** Send STUN request. */ void send_stun_request(void); /** Cleanup STUN data if not in use anymore. */ void cleanup_stun_data(void); /** Stop sending NAT keep alives when not necessary anymore. */ void cleanup_nat_keepalive(void); /** * Synchronize the sending of NAT keep alives with the user config. * Start sending if keep alives are enabled but currently not being * sent. */ void sync_nat_keepalive(void); /** Stop sending TCP ping packets when not necessary anumore. */ void cleanup_tcp_ping(void); /** @name Getters */ //@{ t_user *get_user_profile(void); t_buddy_list *get_buddy_list(void); t_presence_epa *get_presence_epa(void); //@} // Handle responses for out-of-dialog requests void handle_response_out_of_dialog(t_response *r, t_tuid tuid, t_tid tid); void handle_response_out_of_dialog(StunMessage *r, t_tuid tuid); /** * Send a registration, de-registration or query registration request. * @param register_type [in] Type of registration request. * @param re_register [in] Indicates if this registration request is a re-registration. * @param expires [in] Epxiry time to put in registration request. * @note If needed a STUN request is sent before doing a registration. */ void registration(t_register_type register_type, bool re_register, unsigned long expires = 0); // OPTIONS outside dialog void options(const t_url &to_uri, const string &to_display = ""); /** @name MWI */ //@{ /** Subscribe to MWI. */ void subscribe_mwi(void); /** Unsusbcribe to MWI. */ void unsubscribe_mwi(void); /** * Check if an MWI subscription is established. * @return true, if an MWI subscription is established. * @return false, otherwise */ bool is_mwi_subscribed(void) const; /** * Check if an MWI dialog does exist. * @return true, if there is no MWI subscription dialog. * @return false, otherwise */ bool is_mwi_terminated(void) const; /** * Process an unsollicited NOTIFY for MWI. * @param r [in] The NOTIFY request. * @param tid [in] Transaction identifier of the NOTIFY transaction. */ void handle_mwi_unsollicited(t_request *r, t_tid tid); //@} /** @name Presence */ //@{ /** Subscribe to presence of buddies in buddy list. */ void subscribe_presence(void); /** Unsusbcribe to presence of buddies in buddy list. */ void unsubscribe_presence(void); /** * Check if all presence subscriptions are terminated. * @return true, if all presence subscriptions are terminated. * @return false, otherwise */ bool is_presence_terminated(void) const; /** * Publish presence. * @param basic_state [in] The basic presence state to publish. */ void publish_presence(t_presence_state::t_basic_state basic_state); /** Unpublish presence. */ void unpublish_presence(void); //@} /** * Send a text message. * @param to_uri [in] Destination URI of recipient. * @param to_display [in] Display name of recipient. * @param msg [in] The message to send. * @return True if sending succeeded, otherwise false. */ bool send_message(const t_url &to_uri, const string &to_display, const t_msg &message); /** * @param to_uri [in] Destination URI of recipient. * @param to_display [in] Display name of recipient. * @param state [in] Message composing state. * @param refresh [in] The refresh interval in seconds (when state is active). * @return True if sending succeeded, false otherwise. * @note For the idle state, the value of refresh has no meaning. */ bool send_im_iscomposing(const t_url &to_uri, const string &to_display, const string &state, time_t refresh); /** * Process incoming MESSAGE request. * @param r [in] The MESSAGE request. * @param tid [in] Transaction id of the request transaction. */ void recvd_message(t_request *r, t_tid tid); /** * Process incoming NOTIFY reqeust. * @param r [in] The NOTIFY request. * @param tid [in] Transaction id of the request transaction. */ void recvd_notify(t_request *r, t_tid tid); /** @name Process timeouts */ //@{ /** * Process phone timer expiry. * @param timer [in] Type of expired phone timer. */ void timeout(t_phone_timer timer); /** * Proces subscribe timer expiry. * @param timer [in] Type of expired subscribe timer. * @param id_timer [in] Id of expired timer. */ void timeout_sub(t_subscribe_timer timer, t_object_id id_timer); /** * Proces publish timer expiry. * @param timer [in] Type of expired subscribe timer. * @param id_timer [in] Id of expired timer. */ void timeout_publish(t_publish_timer timer, t_object_id id_timer); //@} /** Handle a broken persistent connection. */ void handle_broken_connection(void); /** Match subscribe timeout with a subcription * @param timer [in] Type of expired subscribe timer. * @param id_timer [in] Id of expired timer. * @return True if timer matches a subscription owned by the phone user. * @return False, otherwise. */ bool match_subscribe_timer(t_subscribe_timer timer, t_object_id id_timer) const; /** Match publish timeout with a subcription * @param timer [in] Type of expired publish timer. * @param id_timer [in] Id of expired timer. * @return True if timer matches a publication owned by the phone user. * @return False, otherwise. */ bool match_publish_timer(t_publish_timer timer, t_object_id id_timer) const; /** * Start the re-subscribe timer after an MWI subscription failure. * @param duration [in] Duration before trying a re-subscribe (s) */ void start_resubscribe_mwi_timer(unsigned long duration); /** Stop MWI re=subscribe timer. */ void stop_resubscribe_mwi_timer(void); /** * Create request. * Headers that are the same for each request * are already populated: Via, From, Max-Forwards, User-Agent. * All possible destinations for a failover are calculated. * @param m [in] Request method. * @param request_uri [in] Request-URI. * @return The created request. */ t_request *create_request(t_method m, const t_url &request_uri) const; // Create a response to an OPTIONS request // Argument 'in-dialog' indicates if the OPTIONS response is // sent within a dialog. t_response *create_options_response(t_request *r, bool in_dialog = false) const; // Get registration status bool get_is_registered(void) const; bool get_last_reg_failed(void) const; /** * Get local IP address for SIP. * @param auto_ip [in] IP address to use if no IP address has been determined through * some NAT procedure. * @return The IP address. */ string get_ip_sip(const string &auto_ip) const; /** * Get local port for SIP. * @return SIP port. */ unsigned short get_public_port_sip(void) const; /** * Get the service route. * @return The service route. */ list get_service_route(void) const; // Try to match message with phone user bool match(t_response *r, t_tuid tuid) const; bool match(t_request *r) const; bool match(StunMessage *r, t_tuid tuid) const; /** * Authorize the request based on the challenge in the response * @param r [inout] The request to be authorized. * @param resp [in] The response containing the challenge (401/407). * @param True if authorization succeeds, false otherwise. * @post On successful return the request r contains the correct authorization * header (based on 401/407 response). */ bool authorize(t_request *r, t_response *resp); /** * Resend the request: a new sequence number will be assigned and a new via * header created (new transaction). * @param req [in] The request to resend. * @param cr [in] is the current client request wrapper for this request. * @note In case of a REGISTER, the internal register seqnr will be increased. */ void resend_request(t_request *req, t_client_request *cr); /** * Remove cached credentials for a particular realm. * @param realm [in] The realm. */ void remove_cached_credentials(const string &realm); /** * Check if this phone user is active. * @return True if phone user is active, false otherwise. */ bool is_active(void) const; /** * Activate phone user. * @param user [in] The user profile of the user. * @note The passed user profile will replace the current user profile * owned by phone user. During the deactivated state the profile may * have been update. */ void activate(const t_user &user); /** Deactivate phone user. */ void deactivate(void); }; #endif twinkle-1.10.1/src/presence/000077500000000000000000000000001277565361200156605ustar00rootroot00000000000000twinkle-1.10.1/src/presence/CMakeLists.txt000066400000000000000000000003621277565361200204210ustar00rootroot00000000000000project(libtwinkle-presence) set(LIBTWINKLE_PRESENCE-SRCS buddy.cpp pidf_body.cpp presence_dialog.cpp presence_epa.cpp presence_state.cpp presence_subscription.cpp ) add_library(libtwinkle-presence OBJECT ${LIBTWINKLE_PRESENCE-SRCS}) twinkle-1.10.1/src/presence/buddy.cpp000066400000000000000000000333551277565361200175040ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "buddy.h" #include #include "log.h" #include "phone.h" #include "phone_user.h" #include "userintf.h" #include "audits/memman.h" extern t_phone *phone; extern t_event_queue *evq_timekeeper; /** Buddy */ void t_buddy::cleanup_presence_dialog(void) { assert(phone_user); if (presence_dialog && presence_dialog->get_subscription_state() == SS_TERMINATED) { string reason_termination = presence_dialog->get_reason_termination(); bool may_resubscribe = presence_dialog->get_may_resubscribe(); unsigned long dur_resubscribe = presence_dialog->get_resubscribe_after(); MEMMAN_DELETE(presence_dialog); delete presence_dialog; presence_dialog = NULL; phone_user->stun_binding_inuse_presence--; phone_user->cleanup_stun_data(); phone_user->cleanup_nat_keepalive(); if (presence_auto_resubscribe) { if (may_resubscribe) { if (dur_resubscribe > 0) { start_resubscribe_presence_timer(dur_resubscribe * 1000); } else { subscribe_presence(); } } else if (reason_termination.empty()) { start_resubscribe_presence_timer(DUR_PRESENCE_FAILURE * 1000); } } } } t_buddy::t_buddy() : phone_user(NULL), may_subscribe_presence(false), presence_state(this), presence_dialog(NULL), subscribe_after_stun(false), presence_auto_resubscribe(false), delete_after_presence_terminated(false), id_resubscribe_presence(0) { } t_buddy::t_buddy(t_phone_user *_phone_user) : phone_user(_phone_user), may_subscribe_presence(false), presence_state(this), presence_dialog(NULL), subscribe_after_stun(false), presence_auto_resubscribe(false), delete_after_presence_terminated(false), id_resubscribe_presence(0) { } t_buddy::t_buddy(t_phone_user *_phone_user, const string _name, const string &_sip_address) : phone_user(_phone_user), name(_name), sip_address(_sip_address), may_subscribe_presence(false), presence_state(this), presence_dialog(NULL), subscribe_after_stun(false), presence_auto_resubscribe(false), delete_after_presence_terminated(false), id_resubscribe_presence(0) { } t_buddy::t_buddy(const t_buddy &other) : phone_user(other.phone_user), name(other.name), sip_address(other.sip_address), may_subscribe_presence(other.may_subscribe_presence), presence_state(this), presence_dialog(NULL), subscribe_after_stun(false), presence_auto_resubscribe(false), delete_after_presence_terminated(false), id_resubscribe_presence(0) {} t_buddy::~t_buddy() { if (presence_dialog) { MEMMAN_DELETE(presence_dialog); delete presence_dialog; } } string t_buddy::get_name(void) const { return name; } string t_buddy::get_sip_address(void) const { return sip_address; } bool t_buddy::get_may_subscribe_presence(void) const { return may_subscribe_presence; } const t_presence_state *t_buddy::get_presence_state(void) const { return &presence_state; } t_user *t_buddy::get_user_profile(void) { assert(phone_user); return phone_user->get_user_profile(); } t_buddy_list *t_buddy::get_buddy_list(void) { assert(phone_user); return phone_user->get_buddy_list(); } void t_buddy::set_phone_user(t_phone_user *_phone_user) { phone_user = _phone_user; } void t_buddy::set_name(const string &_name) { name = _name; notify(); } void t_buddy::set_sip_address(const string &_sip_address) { sip_address = _sip_address; notify(); } void t_buddy::set_may_subscribe_presence(bool _may_subscribe_presence) { may_subscribe_presence = _may_subscribe_presence; notify(); } bool t_buddy::match_response(t_response *r, t_tuid tuid) const { return (presence_dialog && presence_dialog->match_response(r, tuid)); } bool t_buddy::match_request(t_request *r) const { if (!presence_dialog) return false; bool partial_match = false; bool match = presence_dialog->match_request(r, partial_match); if (match) return true; if (partial_match && presence_dialog->get_remote_tag().empty()) { // A NOTIFY may be received before a 2XX on SUBSCRIBE. // In this case the NOTIFY will establish the dialog. return true; } return false; } bool t_buddy::match_timer(t_subscribe_timer timer, t_object_id id_timer) const { if (presence_dialog && presence_dialog->match_timer(timer, id_timer)) { return true; } return id_timer == id_resubscribe_presence; } void t_buddy::timeout(t_subscribe_timer timer, t_object_id id_timer) { switch (timer) { case STMR_SUBSCRIPTION: if (presence_dialog && presence_dialog->match_timer(timer, id_timer)) { (void)presence_dialog->timeout(timer); cleanup_presence_dialog(); } else if (id_timer == id_resubscribe_presence) { // Try to subscribe to presence id_resubscribe_presence = 0; subscribe_presence(); } break; default: assert(false); } } void t_buddy::recvd_response(t_response *r, t_tuid tuid, t_tid tid) { if (presence_dialog) { presence_dialog->recvd_response(r, tuid, tid); cleanup_presence_dialog(); } } void t_buddy::recvd_request(t_request *r, t_tuid tuid, t_tid tid) { if (presence_dialog) { presence_dialog->recvd_request(r, tuid, tid); cleanup_presence_dialog(); } } void t_buddy::start_resubscribe_presence_timer(unsigned long duration) { t_tmr_subscribe *t; t = new t_tmr_subscribe(duration, STMR_SUBSCRIPTION, 0, 0, SIP_EVENT_PRESENCE, ""); MEMMAN_NEW(t); id_resubscribe_presence = t->get_object_id(); evq_timekeeper->push_start_timer(t); MEMMAN_DELETE(t); delete t; } void t_buddy::stop_resubscribe_presence_timer(void) { if (id_resubscribe_presence != 0) { evq_timekeeper->push_stop_timer(id_resubscribe_presence); id_resubscribe_presence = 0; } } void t_buddy::stun_completed(void) { if (subscribe_after_stun) { subscribe_after_stun = false; subscribe_presence(); } } void t_buddy::stun_failed(void) { if (subscribe_after_stun) { subscribe_after_stun = false; start_resubscribe_presence_timer(DUR_PRESENCE_FAILURE * 1000); } } void t_buddy::subscribe_presence(void) { assert(phone_user); t_user *user_config = phone_user->get_user_profile(); if (!may_subscribe_presence) return; presence_auto_resubscribe = true; if (presence_dialog) { // Already subscribed. log_file->write_header("t_buddy::subscribe_presence", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Already subscribed to presence: "); log_file->write_raw(name); log_file->write_raw(", "); log_file->write_raw(sip_address); log_file->write_endl(); log_file->write_footer(); return; } // If STUN is enabled, then do a STUN query before registering to // determine the public IP address. if (phone_user->use_stun) { if (phone_user->stun_public_ip_sip == 0) { phone_user->send_stun_request(); phone_user->presence_subscribe_after_stun = true; subscribe_after_stun = true; return; } phone_user->stun_binding_inuse_presence++; } presence_dialog = new t_presence_dialog(phone_user, &presence_state); MEMMAN_NEW(presence_dialog); string dest = ui->expand_destination(user_config, sip_address); t_url dest_url(dest); if (!dest_url.is_valid()) { log_file->write_header("t_buddy::subscribe_presence", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Invalid SIP address: "); log_file->write_raw(sip_address); log_file->write_endl(); log_file->write_footer(); return; } presence_dialog->subscribe(DUR_PRESENCE(user_config), dest_url, dest_url, ""); // Start sending NAT keepalive packets when STUN is used // (or in case of symmetric firewall) if (phone_user->use_nat_keepalive && phone_user->id_nat_keepalive == 0) { // Just start the NAT keepalive timer. The SUBSCRIBE // message will create the NAT binding. So there is // no need to send a NAT keep alive packet now. phone->start_timer(PTMR_NAT_KEEPALIVE, phone_user); } cleanup_presence_dialog(); } void t_buddy::unsubscribe_presence(bool remove) { presence_auto_resubscribe = false; stop_resubscribe_presence_timer(); presence_state.set_basic_state(t_presence_state::ST_BASIC_UNKNOWN); delete_after_presence_terminated = remove; if (presence_dialog) { presence_dialog->unsubscribe(); cleanup_presence_dialog(); } } bool t_buddy::create_file_record(vector &v) const { if (delete_after_presence_terminated) return false; v.clear(); v.push_back(name); v.push_back(sip_address); v.push_back((may_subscribe_presence ? "y" : "n")); return true; } bool t_buddy::populate_from_file_record(const vector &v) { if (v.size() !=3 ) return false; name = v[0]; sip_address = v[1]; may_subscribe_presence = (v[2] == "y"); return true; } bool t_buddy::operator==(const t_buddy &other) const { return (name == other.name && sip_address == other.sip_address); } void t_buddy::clear_presence(void) { if (id_resubscribe_presence) stop_resubscribe_presence_timer(); if (presence_dialog) { MEMMAN_DELETE(presence_dialog); delete presence_dialog; presence_dialog = NULL; } presence_state.set_basic_state(t_presence_state::ST_BASIC_UNKNOWN); } bool t_buddy::is_presence_terminated(void) const { return presence_dialog == NULL; } bool t_buddy::must_delete_now(void) const { return delete_after_presence_terminated && is_presence_terminated(); } /** Buddy list */ void t_buddy_list::add_record(const t_buddy &record) { t_buddy r(record); r.set_phone_user(phone_user); utils::t_record_file::add_record(r); } t_buddy_list::t_buddy_list(t_phone_user *_phone_user) : phone_user(_phone_user), is_subscribed(false) { t_user *user_config = phone_user->get_user_profile(); set_header("name|sip_address|subscribe"); set_separator('|'); string filename = user_config->get_profile_name() + BUDDY_FILE_EXT; string f = user_config->expand_filename(filename); set_filename(f); } t_user *t_buddy_list::get_user_profile(void) { return phone_user->get_user_profile(); } t_buddy *t_buddy_list::add_buddy(const t_buddy &buddy) { t_buddy *b = NULL; mtx_records.lock(); add_record(buddy); // KLUDGE: this code assumes that the buddy is added at the end. b = &records.back(); mtx_records.unlock(); log_file->write_header("t_buddy_list::add_buddy"); log_file->write_raw("Added buddy: "); log_file->write_raw(b->get_name()); log_file->write_raw(", "); log_file->write_raw(b->get_sip_address()); log_file->write_endl(); log_file->write_footer(); return b; } void t_buddy_list::del_buddy(const t_buddy &buddy) { mtx_records.lock(); list::iterator it = find(records.begin(), records.end(), buddy); if (it == records.end()) { mtx_records.unlock(); return; } log_file->write_header("t_buddy_list::del_buddy"); log_file->write_raw("Delete buddy: "); log_file->write_raw(buddy.get_name()); log_file->write_raw(", "); log_file->write_raw(buddy.get_sip_address()); log_file->write_endl(); log_file->write_footer(); records.erase(it); mtx_records.unlock(); } bool t_buddy_list::match_response(t_response *r, t_tuid tuid, t_buddy **buddy) { *buddy = NULL; mtx_records.lock(); for (list::iterator it = records.begin(); it != records.end(); ++it) { if (it->match_response(r, tuid)) { *buddy = &(*it); break; } } mtx_records.unlock(); return *buddy != NULL; } bool t_buddy_list::match_request(t_request *r, t_buddy **buddy) { *buddy = NULL; mtx_records.lock(); for (list::iterator it = records.begin(); it != records.end(); ++it) { if (it->match_request(r)) { *buddy = &(*it); break; } } mtx_records.unlock(); return *buddy != NULL; } bool t_buddy_list::match_timer(t_subscribe_timer timer, t_object_id id_timer, t_buddy **buddy) { *buddy = NULL; mtx_records.lock(); for (list::iterator it = records.begin(); it != records.end(); ++it) { if (it->match_timer(timer, id_timer)) { *buddy = &(*it); break; } } mtx_records.unlock(); return *buddy != NULL; } void t_buddy_list::stun_completed(void) { mtx_records.lock(); for (list::iterator it = records.begin(); it != records.end(); ++it) { it->stun_completed(); } mtx_records.unlock(); } void t_buddy_list::stun_failed(void) { mtx_records.lock(); for (list::iterator it = records.begin(); it != records.end(); ++it) { it->stun_failed(); } mtx_records.unlock(); } void t_buddy_list::subscribe_presence(void) { mtx_records.lock(); for (list::iterator it = records.begin(); it != records.end(); ++it) { it->subscribe_presence(); } is_subscribed = true; mtx_records.unlock(); } void t_buddy_list::unsubscribe_presence(void) { mtx_records.lock(); for (list::iterator it = records.begin(); it != records.end(); ++it) { it->unsubscribe_presence(); } is_subscribed = false; mtx_records.unlock(); } bool t_buddy_list::get_is_subscribed() const { bool result; mtx_records.lock(); result = is_subscribed; mtx_records.unlock(); return result; } void t_buddy_list::clear_presence(void) { mtx_records.lock(); for (list::iterator it = records.begin(); it != records.end(); ++it) { it->clear_presence(); } is_subscribed = false; mtx_records.unlock(); } bool t_buddy_list::is_presence_terminated(void) const { bool result = true; mtx_records.lock(); for (list::const_iterator it = records.begin(); it != records.end(); ++it) { if (!it->is_presence_terminated()) { result = false; break; } } mtx_records.unlock(); return result; } twinkle-1.10.1/src/presence/buddy.h000066400000000000000000000225441277565361200171470ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Buddy list */ #ifndef _BUDDY_H #define _BUDDY_H #include #include #include "presence_state.h" #include "presence_dialog.h" #include "sockets/url.h" #include "utils/record_file.h" #include "patterns/observer.h" // Forward declaration class t_phone_user; class t_buddy_list; #define BUDDY_FILE_EXT ".bud" using namespace std; /** Buddy */ class t_buddy : public utils::t_record, public patterns::t_subject { private: /** Phone user owning this buddy. */ t_phone_user *phone_user; /** Name of buddy (for display only) */ string name; /** SIP address of the buddy. */ string sip_address; /** Indicates if the user may subscribe to the presence state of the buddy. */ bool may_subscribe_presence; /** Presence state. */ t_presence_state presence_state; /** Presence subscription dialog. */ t_presence_dialog *presence_dialog; /** Subscribe to presence after STUN transaction completed. */ bool subscribe_after_stun; /** * Indicates if presence must be automatically resubscribed to, if the * subscription terminates with a reason telling that resubscription * is possible. */ bool presence_auto_resubscribe; /** * Indicates if the buddy must be deleted after the presence subscription * has been terminated. */ bool delete_after_presence_terminated; /** Handle presence dialog termination. */ void cleanup_presence_dialog(void); public: /** Interval before trying to resubscribe to presence after a failure. */ t_object_id id_resubscribe_presence; /** Constructor. */ t_buddy(); /** Constructor. */ t_buddy(t_phone_user *_phone_user); /** Constructor. */ t_buddy(t_phone_user *_phone_user, const string _name, const string &_sip_address); /** Copy constructor. */ t_buddy(const t_buddy &other); /** Destructor. */ virtual ~t_buddy(); /** @name Getters */ //@{ string get_name(void) const; string get_sip_address(void) const; bool get_may_subscribe_presence(void) const; const t_presence_state *get_presence_state(void) const; //@} /** * Get user profile for the user owning this buddy. * @return User profile. */ t_user *get_user_profile(void); /** * Get the buddy list containing this buddy. * @return Buddy list */ t_buddy_list *get_buddy_list(void); /** @name Setters */ //@{ void set_phone_user(t_phone_user *_phone_user); void set_name(const string &_name); void set_sip_address(const string &_sip_address); void set_may_subscribe_presence(bool _may_subscribe_presence); //@} /** * Match response with a buddy. It matches if the buddy * has a presence dialog that matches with the response. * @param r [in] The response. * @param tuid [in] Transaction user id. * @return True if the response matches, otherwise false. */ bool match_response(t_response *r, t_tuid tuid) const; /** * Match request with buddy list. It matches if a buddy in the list * has a presence dialog that matches with the request. * @param r [in] The request. * @return True if the request matches, otherwise false. */ bool match_request(t_request *r) const; /** * Match a timer id with a running timer. * @param timer [in] The running timer. * @param id_timer [in] The timer id. * @return true, if timer id matches with timer. * @return false, otherwise. */ bool match_timer(t_subscribe_timer timer, t_object_id id_timer) const; /** * Process timeout. * @param timer [in] The timer that expired. * @param id_timer [in] The timer id. */ void timeout(t_subscribe_timer timer, t_object_id id_timer); /** * Handle received response. * @param r [in] The response. * @param tuid [in] Transaction user id. * @param tid [in] Transaction id. */ void recvd_response(t_response *r, t_tuid tuid, t_tid tid); /** * Handle received request. * @param r [in] The request. * @param tuid [in] Transaction user id. * @param tid [in] Transaction id. */ void recvd_request(t_request *r, t_tuid tuid, t_tid tid); /** * Start the re-subscribe timer after a presence subscription failure. * @param duration [in] Duration before trying a re-subscribe (s) */ void start_resubscribe_presence_timer(unsigned long duration); /** Stop presence re-subscribe timer. */ void stop_resubscribe_presence_timer(void); /** * By calling this method, successful STUN completion is signalled to * the buddy. It will subscribe to presence if it was waiting for STUN. */ void stun_completed(void); /** * By calling this method, a STUN failure is signalled to * the buddy. It will reschedule a presence subscription if it is * waiting for STUN to complete. */ void stun_failed(void); /** Subscribe to presence of the buddy if we may do so. */ void subscribe_presence(void); /** Unsubscribe to presence. * @param remove [in] Indicates if the buddy must be deleted after unsubscription. */ void unsubscribe_presence(bool remove = false); virtual bool create_file_record(vector &v) const; virtual bool populate_from_file_record(const vector &v); /** Compare 2 buddies for equality (same SIP address) */ bool operator==(const t_buddy &other) const; /** Clear presence state. */ void clear_presence(void); /** * Check if presence subscription is terminated. * @return true, if presence subscriptions is terminated. * @return false, otherwise */ bool is_presence_terminated(void) const; /** * Check if buddy must be deleted. * @return true, if the buddy must be deleted immediately. * @return false, otherwise. */ bool must_delete_now(void) const; }; /** List of buddies for a particular account. */ class t_buddy_list : public utils::t_record_file { private: /** Phone user owning this buddy list. */ t_phone_user *phone_user; /** * Indicates if subscribe is done. This indicator will be set to false * when you call unsubscribe. */ bool is_subscribed; protected: virtual void add_record(const t_buddy &record); public: /** Constructor. */ t_buddy_list(t_phone_user *_phone_user); /** * Get the user profile for this buddy list. * @return User profile. */ t_user *get_user_profile(void); /** * Add a buddy. * @param buddy [in] Buddy to add. * @return Pointer to added buddy. * @note This method adds a copy of the buddy. It returns a pointer to this copy. */ t_buddy *add_buddy(const t_buddy &buddy); /** * Delete a buddy. * @param buddy [in] Buddy to delete. */ void del_buddy(const t_buddy &buddy); /** * Match response with buddy list. It matches if a buddy in the list * has a presence dialog that matches with the response. * @param r [in] The response. * @param tuid [in] Transaction user id. * @param buddy [out] On a match, this parameter contains the matching buddy. * @return True if the response matches, otherwise false. */ bool match_response(t_response *r, t_tuid tuid, t_buddy **buddy); /** * Match request with buddy list. It matches if a buddy in the list * has a presence dialog that matches with the request. * @param r [in] The request. * @param buddy [out] On a match, this parameter contains the matching buddy. * @return True if the request matches, otherwise false. */ bool match_request(t_request *r, t_buddy **buddy); /** * Match a timer id with a running timer. A timer id matches with the * buddy list if it matches with one of the buddies in the list. * @param timer [in] The running timer. * @param id_timer [in] The timer id. * @param buddy [out] On a match, this parameter contains the matching buddy. * @return true, if timer id matches with timer. * @return false, otherwise. */ bool match_timer(t_subscribe_timer timer, t_object_id id_timer, t_buddy **buddy); /** * By calling this method, successful STUN completion is signalled to the buddy * list. The buddy list will now start presence subscriptions that were waiting * for STUN to complete. */ void stun_completed(void); /** * By calling this method, a STUN failure is signalled to the buddy list. * The buddy list will reschedule presence subscriptions that were waiting * for STUN to complete. */ void stun_failed(void); /** Subscribe to presence of all buddies in the list. */ void subscribe_presence(void); /** Unsubscribe to presence of all buddies in the list. */ void unsubscribe_presence(void); /** * Check if user is subcribed to buddy list presence. * @return True if subscribed, otherwise false. */ bool get_is_subscribed() const; /** Clear presence state of all buddies. */ void clear_presence(void); /** * Check if all presence subscriptions are terminated. * @return true, if all presence subscriptions are terminated. * @return false, otherwise */ bool is_presence_terminated(void) const; }; #endif twinkle-1.10.1/src/presence/pidf_body.cpp000066400000000000000000000131751277565361200203320ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "pidf_body.h" #include #include #include "log.h" #include "util.h" #include "audits/memman.h" #define PIDF_XML_VERSION "1.0" #define PIDF_NAMESPACE "urn:ietf:params:xml:ns:pidf" #define IS_PIDF_TAG(node, tag) IS_XML_TAG(node, tag, PIDF_NAMESPACE) #define IS_PIDF_ATTR(attr, attr_name) IS_XML_ATTR(attr, attr_name, PIDF_NAMESPACE) bool t_pidf_xml_body::extract_status(void) { assert(xml_doc); xmlNode *root_element = NULL; // Get root root_element = xmlDocGetRootElement(xml_doc); if (!root_element) { log_file->write_report("PIDF document has no root element.", "t_pidf_xml_body::extract_status", LOG_NORMAL, LOG_WARNING); return false; } // Check if root is if (!IS_PIDF_TAG(root_element, "presence")) { log_file->write_report("PIDF document has invalid root element.", "t_pidf_xml_body::extract_status", LOG_NORMAL, LOG_WARNING); return false; } pres_entity.clear(); tuple_id.clear(); basic_status.clear(); // Get presence entity xmlChar *prop_entity = xmlGetProp(root_element, BAD_CAST "entity"); if (prop_entity) { pres_entity = (char *)prop_entity; } else { log_file->write_report("Presence entity is missing.", "t_pidf_xml_body::extract_status", LOG_NORMAL, LOG_WARNING); } xmlNode *child = root_element->children; // Process children of root. for (xmlNode *cur_node = child; cur_node; cur_node = cur_node->next) { // Process tuple if (IS_PIDF_TAG(cur_node, "tuple")) { process_pidf_tuple(cur_node); // Process the first tuple and then stop. // Currently there is no support for multiple tuples // or additional elements. break; } } return true; } void t_pidf_xml_body::process_pidf_tuple(xmlNode *tuple) { assert(tuple); // Get tuple id. xmlChar *id = xmlGetProp(tuple, BAD_CAST "id"); if (id) { tuple_id = (char *)id; } else { log_file->write_report("Tuple id is missing.", "t_pidf_xml_body::process_pidf_tuple", LOG_NORMAL, LOG_WARNING); } // Find status element xmlNode *child = tuple->children; for (xmlNode *cur_node = child; cur_node; cur_node = cur_node->next) { // Process status if (IS_PIDF_TAG(cur_node, "status")) { process_pidf_status(cur_node); break; } } } void t_pidf_xml_body::process_pidf_status(xmlNode *status) { assert(status); xmlNode *child = status->children; for (xmlNode *cur_node = child; cur_node; cur_node = cur_node->next) { // Process status if (IS_PIDF_TAG(cur_node, "basic")) { process_pidf_basic(cur_node); break; } } } void t_pidf_xml_body::process_pidf_basic(xmlNode *basic) { assert(basic); xmlNode *child = basic->children; if (child && child->type == XML_TEXT_NODE) { basic_status = tolower((char*)child->content); } else { log_file->write_report(" element has no content.", "t_pidf_xml_body::process_pidf_basic", LOG_NORMAL, LOG_WARNING); } } void t_pidf_xml_body::create_xml_doc(const string &xml_version, const string &charset) { t_sip_body_xml::create_xml_doc(xml_version, charset); // presence xmlNode *node_presence = xmlNewNode(NULL, BAD_CAST "presence"); xmlNs *ns_pidf = xmlNewNs(node_presence, BAD_CAST PIDF_NAMESPACE, NULL); xmlNewProp(node_presence, BAD_CAST "entity", BAD_CAST pres_entity.c_str()); xmlDocSetRootElement(xml_doc, node_presence); // tuple xmlNode *node_tuple = xmlNewChild(node_presence, ns_pidf, BAD_CAST "tuple", NULL); xmlNewProp(node_tuple, BAD_CAST "id", BAD_CAST tuple_id.c_str()); // status xmlNode *node_status = xmlNewChild(node_tuple, ns_pidf, BAD_CAST "status", NULL); // basic xmlNewChild(node_status, ns_pidf, BAD_CAST "basic", BAD_CAST basic_status.c_str()); } t_pidf_xml_body::t_pidf_xml_body() : t_sip_body_xml () {} t_sip_body *t_pidf_xml_body::copy(void) const { t_pidf_xml_body *body = new t_pidf_xml_body(*this); MEMMAN_NEW(body); // Clear the xml_doc pointer in the new body, as a copy of the // XML document must be copied to the body. body->xml_doc = NULL; copy_xml_doc(body); return body; } t_body_type t_pidf_xml_body::get_type(void) const { return BODY_PIDF_XML; } t_media t_pidf_xml_body::get_media(void) const { return t_media("application", "pidf+xml"); } string t_pidf_xml_body::get_pres_entity(void) const { return pres_entity; } string t_pidf_xml_body::get_tuple_id(void) const { return tuple_id; } string t_pidf_xml_body::get_basic_status(void) const { return basic_status; } void t_pidf_xml_body::set_pres_entity(const string &_pres_entity) { clear_xml_doc(); pres_entity = _pres_entity; } void t_pidf_xml_body::set_tuple_id(const string &_tuple_id) { clear_xml_doc(); tuple_id = _tuple_id; } void t_pidf_xml_body::set_basic_status(const string &_basic_status) { clear_xml_doc(); basic_status = _basic_status; } bool t_pidf_xml_body::parse(const string &s) { if (t_sip_body_xml::parse(s)) { if (!extract_status()) { MEMMAN_DELETE(xml_doc); xmlFreeDoc(xml_doc); xml_doc = NULL; } } return (xml_doc != NULL); } twinkle-1.10.1/src/presence/pidf_body.h000066400000000000000000000054011277565361200177700ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * RFC 3863 pidf+xml body */ #ifndef _PIDF_BODY_H #define _PIDF_BODY_H #include #include #include "parser/sip_body.h" #define PIDF_STATUS_BASIC_OPEN "open" #define PIDF_STATUS_BASIC_CLOSED "closed" /** RFC 3863 pidf+xml body */ class t_pidf_xml_body : public t_sip_body_xml { private: string pres_entity; /**< Presence entity */ string tuple_id; /**< Id of tuple containing the basic status. */ string basic_status; /**< Value of basic-tag */ /** * Extract the status information from a PIDF document. * This will populate the state attributes. * @return True if PIDF document is valid, otherwise false. * @pre The @ref pidf_doc should contain a valid PIDF document. */ bool extract_status(void); /** * Process tuple element. * @param tuple [in] tuple element. */ void process_pidf_tuple(xmlNode *tuple); /** * Process status element. * @param status [in] status element. */ void process_pidf_status(xmlNode *status); /** * Process basic element. * @param basic [in] basic element. */ void process_pidf_basic(xmlNode *basic); protected: /** * Create a pidf document from the values stored in the attributes. */ virtual void create_xml_doc(const string &xml_version = "1.0", const string &charset = "UTF-8"); public: /** Constructor */ t_pidf_xml_body(); virtual t_sip_body *copy(void) const; virtual t_body_type get_type(void) const; virtual t_media get_media(void) const; /** @name Getters */ //@{ string get_pres_entity(void) const; string get_tuple_id(void) const; string get_basic_status(void) const; //@} /** @name Setters */ //@{ void set_pres_entity(const string &_pres_entity); void set_tuple_id(const string &_tuple_id); void set_basic_status(const string &_basic_status);; //@} /** * Parse a text representation of the body. * If parsing succeeds, then the state is extracted. * @param s [in] Text to parse. * @return True if parsing and state extracting succeeded, false otherwise. */ virtual bool parse(const string &s); }; #endif twinkle-1.10.1/src/presence/presence_dialog.cpp000066400000000000000000000022461277565361200215130ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "presence_dialog.h" #include "presence_subscription.h" #include "phone_user.h" #include "audits/memman.h" t_presence_dialog::t_presence_dialog(t_phone_user *_phone_user, t_presence_state *presence_state) : t_subscription_dialog(_phone_user) { subscription = new t_presence_subscription(this, presence_state); MEMMAN_NEW(subscription); } t_presence_dialog *t_presence_dialog::copy(void) { // Copy is not needed. assert(false); return NULL; } twinkle-1.10.1/src/presence/presence_dialog.h000066400000000000000000000025551277565361200211630ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Dialog for presence subscription (RFC 3856) */ #ifndef _PRESENCE_DIALOG_H #define _PRESENCE_DIALOG_H #include "subscription_dialog.h" #include "presence_state.h" // Forward declaration class t_phone_user; /** Dialog for presence subscription (RFC 3856) */ class t_presence_dialog : public t_subscription_dialog { public: /** * Constructor. * @param _phone_user [in] Phone user owning the dialog. * @param presence_state [in] Presence state that is updated by notification * on this dialog */ t_presence_dialog(t_phone_user *_phone_user, t_presence_state *presence_state); virtual t_presence_dialog *copy(void); }; #endif twinkle-1.10.1/src/presence/presence_epa.cpp000066400000000000000000000043171277565361200210220ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "presence_epa.h" #include "pidf_body.h" #include "audits/memman.h" #include "parser/hdr_event.h" t_presence_epa::t_presence_epa(t_phone_user *pu) : t_epa(pu, SIP_EVENT_PRESENCE, t_url(pu->get_user_profile()->create_user_uri(false))), basic_state(t_presence_state::ST_BASIC_CLOSED), tuple_id(NEW_PIDF_TUPLE_ID) {} t_presence_state::t_basic_state t_presence_epa::get_basic_state(void) const { return basic_state; } bool t_presence_epa::recv_response(t_response *r, t_tuid tuid, t_tid tid) { t_epa::recv_response(r, tuid, tid); // Notify observers so they can get the latest publication state. notify(); return true; } void t_presence_epa::publish_presence(t_presence_state::t_basic_state _basic_state) { if (_basic_state != t_presence_state::ST_BASIC_CLOSED && _basic_state != t_presence_state::ST_BASIC_OPEN) { // Cannot publish internal states. return; } t_user *user_config = phone_user->get_user_profile(); basic_state = _basic_state; // Create PIDF document t_pidf_xml_body *pidf = new t_pidf_xml_body(); MEMMAN_NEW(pidf); pidf->set_pres_entity(user_config->create_user_uri(false)); pidf->set_tuple_id(tuple_id); pidf->set_basic_status(t_presence_state::basic_state2pidf_str(_basic_state)); publish(user_config->get_pres_publication_time(), pidf); // NOTE: the observers will be notified of the state change, when the // PUBLISH response is received. } void t_presence_epa::clear(void) { t_epa::clear(); basic_state = t_presence_state::ST_BASIC_CLOSED; } twinkle-1.10.1/src/presence/presence_epa.h000066400000000000000000000034021277565361200204610ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Presence Event Publication Agent (EPA) [RFC 3903] */ #ifndef _PRESENCE_EPA_H #define _PRESENCE_EPA_H #include #include "epa.h" #include "presence_state.h" #include "patterns/observer.h" using namespace std; /** Presence Event Publication Agent (EPA) [RFC 3903] */ class t_presence_epa : public t_epa, public patterns::t_subject { private: /** Basic presence state. */ t_presence_state::t_basic_state basic_state; /** Tuple id to be put in the PIDF documents. */ string tuple_id; public: /** Constructor */ t_presence_epa(t_phone_user *pu); /** @name Getters */ //@{ t_presence_state::t_basic_state get_basic_state(void) const; //@} virtual bool recv_response(t_response *r, t_tuid tuid, t_tid tid); /** * Publish presence state. * @param _basic_state [in] The basic presence state. * @pre _basic_state must be one of the following values: * - @ref ST_BASIC_CLOSED * - @ref ST_BASIC_OPEN */ void publish_presence(t_presence_state::t_basic_state _basic_state); virtual void clear(void); }; #endif twinkle-1.10.1/src/presence/presence_state.cpp000066400000000000000000000050351277565361200213730ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "presence_state.h" #include #include "buddy.h" #include "pidf_body.h" #include "log.h" string t_presence_state::basic_state2str(t_presence_state::t_basic_state state) { switch (state) { case ST_BASIC_UNKNOWN: return "unknown"; case ST_BASIC_CLOSED: return "closed"; case ST_BASIC_OPEN: return "open"; case ST_BASIC_FAILED: return "failed"; case ST_BASIC_REJECTED: return "rejeceted"; default: return "UNKNOWN"; } } string t_presence_state::basic_state2pidf_str(t_presence_state::t_basic_state state) { if (state == ST_BASIC_OPEN) { return PIDF_STATUS_BASIC_OPEN; } // Convert all other states to "closed". return PIDF_STATUS_BASIC_CLOSED; } t_presence_state::t_presence_state() { assert(false); } t_presence_state::t_presence_state(t_buddy *_buddy) : buddy(_buddy), basic_state(ST_BASIC_UNKNOWN) { } t_presence_state::t_basic_state t_presence_state::get_basic_state(void) const { t_basic_state result; mtx_state.lock(); result = basic_state; mtx_state.unlock(); return result; } string t_presence_state::get_failure_msg(void) const { string result; mtx_state.lock(); result = failure_msg; mtx_state.unlock(); return result; } void t_presence_state::set_basic_state(t_presence_state::t_basic_state state) { mtx_state.lock(); basic_state = state; log_file->write_header("t_presence_state::set_basic_state", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Presence state changed to: "); log_file->write_raw(basic_state2str(basic_state)); log_file->write_endl(); log_file->write_raw(buddy->get_sip_address()); log_file->write_endl(); log_file->write_footer(); mtx_state.unlock(); // Notify the stat change to all observers of the buddy. buddy->notify(); } void t_presence_state::set_failure_msg(const string &msg) { mtx_state.lock(); failure_msg = msg; mtx_state.unlock(); } twinkle-1.10.1/src/presence/presence_state.h000066400000000000000000000046521277565361200210440ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Presence state (RFC 3863) */ #ifndef _PRESENCE_STATE_H #define _PRESENCE_STATE_H #include #include "threads/mutex.h" using namespace std; // Forward declaration class t_buddy; /** Presence state */ class t_presence_state { public: /** Basic state (RFC 3863 4.1.4) */ enum t_basic_state { ST_BASIC_UNKNOWN, /**< Presence state is unknown. */ ST_BASIC_CLOSED, /**< Unable to accept communication. */ ST_BASIC_OPEN, /**< Ready to accept communication. */ ST_BASIC_FAILED, /**< Failed to determine basic state. */ ST_BASIC_REJECTED,/**< Subscription has been rejected. */ }; /** * Convert a basic state to a string representation for internal usage. * @param state [in] A basic state value. * @return String representation of the basic state. */ static string basic_state2str(t_basic_state state); /** * Convert a basic state to a PIDF string representation. * @param state [in] A basic state value. * @return PIDF representation of the basic state. */ static string basic_state2pidf_str(t_basic_state state); private: /** Mutex for concurrent access to the presence state. */ mutable t_mutex mtx_state; /** Buddy owning this state. */ t_buddy *buddy; /** Basic presence state. */ t_basic_state basic_state; /** Detailed failure message */ string failure_msg; /** Protect the default constructor from being used. */ t_presence_state(); public: /** Constructor. */ t_presence_state(t_buddy *_buddy); /** @name Getters */ //@{ t_basic_state get_basic_state(void) const; string get_failure_msg(void) const; //@} /** @name Setters */ //@{ void set_basic_state(t_basic_state state); void set_failure_msg(const string &msg); //@} }; #endif twinkle-1.10.1/src/presence/presence_subscription.cpp000066400000000000000000000113301277565361200227720ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "presence_subscription.h" #include #include "pidf_body.h" #include "log.h" #include "util.h" #include "parser/hdr_event.h" #include "audits/memman.h" t_request *t_presence_subscription::create_subscribe(unsigned long expires) const { t_request *r = t_subscription::create_subscribe(expires); SET_PRESENCE_HDR_ACCEPT(r->hdr_accept); return r; } t_presence_subscription::t_presence_subscription(t_presence_dialog *_dialog, t_presence_state *_state) : t_subscription(_dialog, SR_SUBSCRIBER, SIP_EVENT_PRESENCE), presence_state(_state) { } bool t_presence_subscription::recv_notify(t_request *r, t_tuid tuid, t_tid tid) { if (t_subscription::recv_notify(r, tuid, tid)) return true; bool unsupported_body = false; // NOTE: if the subscription is still pending (RFC 3265 3.2.4), then the // information in the body has no meaning. // NOTE: a NOTIFY request may have no body (RFC 3856 6.6.2) if (r->body && r->body->get_type() == BODY_PIDF_XML && !is_pending()) { t_pidf_xml_body *body = dynamic_cast(r->body); assert(body); string basic = body->get_basic_status(); if (basic == PIDF_STATUS_BASIC_OPEN) { presence_state->set_basic_state(t_presence_state::ST_BASIC_OPEN); } else if (basic == PIDF_STATUS_BASIC_CLOSED) { presence_state->set_basic_state(t_presence_state::ST_BASIC_CLOSED); } else { log_file->write_header("t_presence_subscription::recv_notify", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Unknown basic status in pidf: "); log_file->write_raw(basic); log_file->write_endl(); log_file->write_footer(); presence_state->set_basic_state(t_presence_state::ST_BASIC_UNKNOWN); } } // Verify if there is an usupported body. if (r->body && r->body->get_type() != BODY_PIDF_XML) { unsupported_body = true; } if (state == SS_TERMINATED) { if (may_resubscribe) { presence_state->set_basic_state(t_presence_state::ST_BASIC_UNKNOWN); log_file->write_report("Presence subscription terminated.", "t_presence_subscription::recv_notify"); } else { if (reason_termination == EV_REASON_REJECTED) { presence_state->set_basic_state(t_presence_state::ST_BASIC_REJECTED); log_file->write_report("Presence agent rejected the subscription.", "t_presence_subscription::recv_notify"); } else { // The PA ended the subscription and indicated // that resubscription is not possible. So no presence status // can be retrieved anymore. This should not happen. // Show it as a failure to the user. presence_state->set_failure_msg(reason_termination); presence_state->set_basic_state(t_presence_state::ST_BASIC_FAILED); log_file->write_report( "Presence agent permanently terminated the subscription.", "t_presence_subscription::recv_notify"); } } } t_response *resp; if (unsupported_body) { resp = r->create_response(R_415_UNSUPPORTED_MEDIA_TYPE); SET_PRESENCE_HDR_ACCEPT(r->hdr_accept); } else { resp = r->create_response(R_200_OK); } send_response(user_config, resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return true; } bool t_presence_subscription::recv_subscribe_response(t_response *r, t_tuid tuid, t_tid tid) { // Parent handles the SUBSCRIBE response (void)t_subscription::recv_subscribe_response(r, tuid, tid); // If the subscription is terminated after the SUBSCRIBE response, it means // that subscription failed. if (state == SS_TERMINATED) { if (r->code == R_403_FORBIDDEN || r->code == R_603_DECLINE) { presence_state->set_basic_state(t_presence_state::ST_BASIC_REJECTED); log_file->write_report("Presence subscription rejected.", "t_presence_subscription::recv_subscribe_response"); } else { string failure = int2str(r->code); failure += ' '; failure += r->reason; presence_state->set_failure_msg(failure); presence_state->set_basic_state(t_presence_state::ST_BASIC_FAILED); log_file->write_report("Presence subscription failed.", "t_presence_subscription::recv_subscribe_response"); } } return true; } twinkle-1.10.1/src/presence/presence_subscription.h000066400000000000000000000030161277565361200224410ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Presence subscription (RFC 3856) */ #ifndef _PRESENCE_SUBSCRIPTION_H #define _PRESENCE_SUBSCRIPTION_H #include "presence_state.h" #include "presence_dialog.h" #include "subscription.h" /** Subscription to the presence event (RFC 3856) */ class t_presence_subscription : public t_subscription { private: t_presence_state *presence_state; protected: virtual t_request *create_subscribe(unsigned long expires) const; public: /** * Constructor. * @param _dialog [in] Dialog for the presence subscription. * @param _state [in] Current presence state. */ t_presence_subscription(t_presence_dialog *_dialog, t_presence_state *_state); virtual bool recv_notify(t_request *r, t_tuid tuid, t_tid tid); virtual bool recv_subscribe_response(t_response *r, t_tuid tuid, t_tid tid); }; #endif twinkle-1.10.1/src/prohibit_thread.cpp000066400000000000000000000024221277565361200177270ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "prohibit_thread.h" void i_prohibit_thread::add_prohibited_thread(void) { prohibited_mutex.lock(); prohibited_threads.insert(t_thread::self()); prohibited_mutex.unlock(); } void i_prohibit_thread::remove_prohibited_thread(void) { prohibited_mutex.lock(); prohibited_threads.erase(t_thread::self()); prohibited_mutex.unlock(); } bool i_prohibit_thread::is_prohibited_thread(void) const { prohibited_mutex.lock(); bool result = (prohibited_threads.find(t_thread::self()) != prohibited_threads.end()); prohibited_mutex.unlock(); return result; } twinkle-1.10.1/src/prohibit_thread.h000066400000000000000000000027061277565361200174010ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _PROHIBIT_THREAD_H #define _PROHIBIT_THREAD_H #include #include "threads/mutex.h" #include "threads/thread.h" using namespace std; // This class implements an interface to keep track of thread id's that are // prohibited from some actions, e.g. taking a certain lock class i_prohibit_thread { private: // List of thread id's that are prohibited from some action mutable t_mutex prohibited_mutex; set prohibited_threads; public: // Operations on the prohibited set of thread id's // Add/remove the thread id of the calling thread void add_prohibited_thread(void); void remove_prohibited_thread(void); // Returns true if the current thread is prohibited bool is_prohibited_thread(void) const; }; #endif twinkle-1.10.1/src/protocol.h000066400000000000000000000267301277565361200160760ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _PROTOCOL_H #define _PROTOCOL_H #include "twinkle_config.h" #include "parser/hdr_supported.h" /** Carriage Return Line Feed */ #define CRLF "\r\n" /** TCP PING packet to be sent on a TCP connection. */ #define TCP_PING_PACKET CRLF CRLF /** Product name */ #define PRODUCT_NAME "Twinkle" /** Product version */ #define PRODUCT_VERSION VERSION /** * When a SIP message is created, some addresses will be filled in * by the sender thread as only this thread knows the source IP * address of an outgoing message. * The sender thread will look for occurrences of the AUTO_IP4_ADDRESS * and replace it with the source IP address. */ #define AUTO_IP4_ADDRESS "255.255.255.255" /** Display name for anonymous calling */ #define ANONYMOUS_DISPLAY "Anonymous" /** SIP-URI for anonymous calling */ #define ANONYMOUS_URI "sip:anonymous@anonymous.invalid" /** Types of failures. */ enum t_failure { FAIL_TIMEOUT, /**< Transaction timed out */ FAIL_TRANSPORT /**< Transport failure */ }; /** Call transfer types */ enum t_transfer_type { TRANSFER_BASIC, /**< Basic transfer (blind) */ TRANSFER_CONSULT, /**< Transfer with consultation (possibly attended) */ TRANSFER_OTHER_LINE /**< Transfer call to other line */ }; /** State of a call transfer at the referrer. */ enum t_refer_state { REFST_NULL, /**< No REFER in progress */ REFST_W4RESP, /**< REFER sent, waiting for response */ REFST_W4NOTIFY, /**< Response received, waiting for 1st NOTIFY */ REFST_PENDING, /**< REFER received, but not granted yet */ REFST_ACTIVE, /**< Referee granted refer */ }; /** Types of registration requests */ enum t_register_type { REG_REGISTER, REG_QUERY, REG_DEREGISTER, REG_DEREGISTER_ALL }; /** * RFC 3261 Annex A * SIP timers */ enum t_sip_timer { TIMER_T1, TIMER_T2, TIMER_T4, TIMER_A, TIMER_B, TIMER_C, TIMER_D, TIMER_E, TIMER_F, TIMER_G, TIMER_H, TIMER_I, TIMER_J, TIMER_K }; // All durations are in msec #define DURATION_T1 500 #define DURATION_T2 4000 #define DURATION_T4 5000 #define DURATION_A DURATION_T1 #define DURATION_B (64 * DURATION_T1) #define DURATION_C 180000 #define DURATION_D 32000 #define DURATION_E DURATION_T1 #define DURATION_F (64 * DURATION_T1) #define DURATION_G DURATION_T1 #define DURATION_H (64 * DURATION_T1) #define DURATION_I DURATION_T4 #define DURATION_J (64 * DURATION_T1) #define DURATION_K DURATION_T4 /** Time to keep an idle connection open before closing */ #define DUR_IDLE_CONNECTION (64 * DURATION_T1) /** UA (phone) timers */ enum t_phone_timer { PTMR_REGISTRATION, /**< Registration (failure) timeout */ PTMR_NAT_KEEPALIVE, /**< NAT binding refresh timeout for STUN */ PTMR_TCP_PING, /**< TCP ping interval */ }; /** UA (line) timers */ enum t_line_timer { LTMR_ACK_TIMEOUT, /**< Waiting for ACK */ LTMR_ACK_GUARD, /**< After this timer ACK is lost for good */ LTMR_INVITE_COMP, /**< After this timer INVITE transiction is considered complete. */ LTMR_NO_ANSWER, /**< This timer expires if the callee does not answer. The call will be torn down. */ LTMR_RE_INVITE_GUARD, /**< re-INVITE timeout */ LTMR_100REL_TIMEOUT, /**< Waiting for PRACK */ LTMR_100REL_GUARD, /**< After this timer PRACK is lost for good */ LTMR_GLARE_RETRY, /**< Waiting before retry re-INVITE after glare */ LTMR_CANCEL_GUARD, /**< Guard for situation where CANCEL has been responded to, but 487 on INVITE is never received. */ }; /** Subscription timers. */ enum t_subscribe_timer { STMR_SUBSCRIPTION, /**< Subscription timeout */ }; /** Publication timers. */ enum t_publish_timer { PUBLISH_TMR_PUBLICATION, /**< Publication timeout */ }; /** STUN timers. */ enum t_stun_timer { STUN_TMR_REQ_TIMEOUT, /**< Waiting for response */ }; /** No answer timer (ms) */ #define DUR_NO_ANSWER(u) ((u)->get_timer_noanswer() * 1000) /** @name Registration timers */ //@{ /** Registration duration (seconds) */ #define DUR_REGISTRATION(u) ((u)->get_registration_time()) /**< Re-register 5 seconds before expiry **/ #define RE_REGISTER_DELTA 5 /** Re-registration interval after reg. failure */ #define DUR_REG_FAILURE 30 //@} /** NAT keepalive timer (s) default value */ #define DUR_NAT_KEEPALIVE 30 /** Default TCP ping interval (s) */ #define DUR_TCP_PING 30 /** * re-INVITE guard timer (ms). This timer guards against the situation * where a UAC has sent a re-INVITE, received a 1XX but never receives * a final response. No timer for this is defined in RFC 3261 */ #define DUR_RE_INVITE_GUARD 10000 /** * Guard for situation where CANCEL has been * responded to, but 487 on INVITE is never eceived. * This situation is not defined by RFC 3261 */ #define DUR_CANCEL_GUARD (64 * DURATION_T1) // MWI timers (s) #define DUR_MWI(u) ((u)->get_mwi_subscription_time()) #define DUR_MWI_FAILURE 30 // Presence timers (s) #define DUR_PRESENCE(u) ((u)->get_pres_subscription_time()) #define DUR_PRESENCE_FAILURE 30 // RFC 3261 14.1 // Maximum values (10th of sec) for timers for retrying a re-INVITE after // a glare (491 response). #define MAX_GLARE_RETRY_NOT_OWN 20 #define MAX_GLARE_RETRY_OWN 40 // Calculate the glare retry duration (ms) #define DUR_GLARE_RETRY_NOT_OWN ((rand() % (MAX_GLARE_RETRY_NOT_OWN + 1)) * 100) #define DUR_GLARE_RETRY_OWN ((rand() % (MAX_GLARE_RETRY_OWN - \ MAX_GLARE_RETRY_NOT_OWN) + 1 + MAX_GLARE_RETRY_NOT_OWN)\ * 100) // RFC 3262 // PRACK timers #define DUR_100REL_TIMEOUT DURATION_T1 #define DUR_100REL_GUARD (64 * DURATION_T1) // refer subscription timer (s) // RFC 3515 does not define the length of the timer. // It should be long enough to notify the result of an INVITE. #define DUR_REFER_SUBSCRIPTION 60 // Used when refer is always permitted #define DUR_REFER_SUB_INTERACT 90 // Used when user has to grant permission // Minimum duration of a subscription #define MIN_DUR_SUBSCRIPTION 60 // Duration to wait before re-subscribing after termination of // a subscription #define DUR_RESUBSCRIBE 30 // After an unsubscribe has been sent, a NOTIFY will should come in. // In case the NOTIFY does not come, this guard timer (ms) will assure // that the subscription will be cleaned up. #define DUR_UNSUBSCRIBE_GUARD 4000 // RFC 3489 // STUN retransmission timer intervals (ms) // The RFC states that the interval should start at 100ms. But that // seems to short. We start at 200 and do 8 instead of 9 transmissions. #define DUR_STUN_START_INTVAL 200 #define DUR_STUN_MAX_INTVAL 1600 // Maximum number of transmissions #define STUN_MAX_TRANSMISSIONS 8 // RFC 3261 #ifndef RFC3261_COOKIE #define RFC3261_COOKIE "z9hG4bK" #endif // Max forwards RFC 3261 8.1.1.6 #define MAX_FORWARDS 70 // Length of tags in from and to headers #define TAG_LEN 5 // Create a new tag #define NEW_TAG random_token(TAG_LEN) // Length of call-id (before domain) #define CALL_ID_LEN 15 // Create a new call-id #define NEW_CALL_ID(u) (random_token(CALL_ID_LEN) + '@' + LOCAL_HOSTNAME) // Create a new sequence number fo CSeq header #define NEW_SEQNR rand() % 1000 + 1 // Length of cnonce #define CNONCE_LEN 10 // Create a cnonce #define NEW_CNONCE random_hexstr(CNONCE_LEN) /** Length of tuple id in PIDF documents. */ #define PIDF_TUPLE_ID_LEN 6 /** Create a new PIDF tuple id. */ #define NEW_PIDF_TUPLE_ID random_token(PIDF_TUPLE_ID_LEN) /** Character set encoding for outgoing text messages */ #define MSG_TEXT_CHARSET "utf-8" /** @name Definitions for akav1-md5 authentication. */ #define AKA_RANDLEN 16 #define AKA_AUTNLEN 16 #define AKA_CKLEN 16 #define AKA_IKLEN 16 #define AKA_AKLEN 6 #define AKA_OPLEN 16 #define AKA_RESLEN 8 #define AKA_SQNLEN 6 #define AKA_RESHEXLEN 16 #define AKA_AMFLEN 2 #define AKA_KLEN 16 // Set Allow header with methods that can be handled by the phone #define SET_HDR_ALLOW(h, u) { (h).add_method(INVITE); \ (h).add_method(ACK); \ (h).add_method(BYE); \ (h).add_method(CANCEL); \ (h).add_method(OPTIONS); \ if ((u)->get_ext_100rel() != EXT_DISABLED) {\ (h).add_method(PRACK);\ }\ (h).add_method(REFER); \ (h).add_method(NOTIFY); \ (h).add_method(SUBSCRIBE); \ (h).add_method(INFO); \ (h).add_method(MESSAGE); \ } // Set Supported header with supported extensions #define SET_HDR_SUPPORTED(h, u) { if ((u)->get_ext_replaces()) {\ (h).add_feature(EXT_REPLACES);\ }\ (h).add_feature(EXT_NOREFERSUB);\ } // Set Accept header with accepted body types #define SET_HDR_ACCEPT(h) { (h).add_media(t_media("application",\ "sdp")); } /** * Check if the content type of an instant message is supported * @param h [in] A SIP message. */ #define MESSAGE_CONTENT_TYPE_SUPPORTED(h)\ ((h).hdr_content_type.media.type == "application" ||\ (h).hdr_content_type.media.type == "audio" ||\ (h).hdr_content_type.media.type == "image" ||\ (h).hdr_content_type.media.type == "text" ||\ (h).hdr_content_type.media.type == "video") /** * Set Accept header with accepted body types for instant messaging. * @param h [inout] A SIP message. */ #define SET_MESSAGE_HDR_ACCEPT(h) { (h).add_media(t_media("application/*"));\ (h).add_media(t_media("audio/*"));\ (h).add_media(t_media("image/*"));\ (h).add_media(t_media("text/*"));\ (h).add_media(t_media("video/*")); } /** * Set Accept header with accepted body types for presence. * @param h [inout] A SIP message. */ #define SET_PRESENCE_HDR_ACCEPT(h) { (h).add_media(t_media("application",\ "pidf+xml")); } /** * Set Accept header with accepted body types for MWI. * @param h [inout] A SIP message. */ #define SET_MWI_HDR_ACCEPT(h) { (h).add_media(t_media("application",\ "simple-message-summary")); } /** * Set Accept-Encoding header with accepted encodings. * @param h [inout] A SIP message. */ #define SET_HDR_ACCEPT_ENCODING(h)\ { (h).add_coding(t_coding("identity")); } /** * Check if content encoding is supported * @param h [inout] A SIP message. */ #define CONTENT_ENCODING_SUPPORTED(ce)\ (cmp_nocase(ce, "identity") == 0) // Set Accept-Language header with accepted languages #define SET_HDR_ACCEPT_LANGUAGE(h)\ { (h).add_language(t_language("en")); } // Set User-Agent header #define SET_HDR_USER_AGENT(h) { (h).add_server(t_server(PRODUCT_NAME,\ PRODUCT_VERSION)); } // Set Server header #define SET_HDR_SERVER(h) { (h).add_server(t_server(PRODUCT_NAME,\ PRODUCT_VERSION)); } // Set Organization header #define SET_HDR_ORGANIZATION(h, u) { if ((u)->get_organization() != "") {\ (h).set_name((u)->get_organization()); }} // Check if an event is supported by Twinkle #define SIP_EVENT_SUPPORTED(e) ((e) == SIP_EVENT_REFER ||\ (e) == SIP_EVENT_MSG_SUMMARY ||\ (e) == SIP_EVENT_PRESENCE) // Add the supported events to the Allow-Events header #define ADD_SUPPORTED_SIP_EVENTS(h) { (h).add_event_type(SIP_EVENT_REFER);\ (h).add_event_type(SIP_EVENT_MSG_SUMMARY);\ (h).add_event_type(SIP_EVENT_PRESENCE); } #endif twinkle-1.10.1/src/redirect.cpp000066400000000000000000000037131277565361200163650ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include "redirect.h" bool t_redirector::contact_already_added(const t_contact_param contact) const { for (list::const_iterator i = try_contacts.begin(); i != try_contacts.end(); i++) { if (i->uri == contact.uri) return true; } for (list::const_iterator i = done_contacts.begin(); i != done_contacts.end(); i++) { if (i->uri == contact.uri) return true; } if (contact.uri == org_dest) return true; return false; } t_redirector::t_redirector(const t_url &_org_dest, int _max_redirections) { num_contacts = 0; org_dest = _org_dest; max_redirections = _max_redirections; } bool t_redirector::get_next_contact(t_contact_param &contact) { if (try_contacts.empty()) return false; contact = try_contacts.front(); try_contacts.pop_front(); done_contacts.push_back(contact); return true; } void t_redirector::add_contacts(const list &contacts) { if (num_contacts >= max_redirections) return; list l = contacts; l.sort(); for (list::iterator i = l.begin(); i != l.end(); i++) { if (!contact_already_added(*i)) { try_contacts.push_back(*i); num_contacts++; if (num_contacts >= max_redirections) break; } } } twinkle-1.10.1/src/redirect.h000066400000000000000000000036551277565361200160370ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _H_REDIRECT #define _H_REDIRECT #include #include "user.h" #include "parser/hdr_contact.h" #include "sockets/url.h" using namespace std; class t_redirector { private: // Total number of contacts in try and done lists int num_contacts; // Contacts to try list try_contacts; // Contacts already tried, but unsuccesful list done_contacts; // Original destination t_url org_dest; // Maximum number of redirections that will be tried int max_redirections; bool contact_already_added(const t_contact_param contact) const; // Constructor without parameter should not be used. t_redirector(); public: t_redirector(const t_url &_org_dest, int _max_redirections); // Get the next contact to try // Returns false if there is no next contact bool get_next_contact(t_contact_param &contact); // Add contacts. The passed contacts will be sorted on decreasing // q-value before adding. // Contacts that are already in the try list or are tried already // will not be added. // If the maximum number of redirections is reached then all contacts // exceeding the maximum will be discarded. void add_contacts(const list &contacts); }; #endif twinkle-1.10.1/src/sdp/000077500000000000000000000000001277565361200146425ustar00rootroot00000000000000twinkle-1.10.1/src/sdp/CMakeLists.txt000066400000000000000000000010531277565361200174010ustar00rootroot00000000000000project(libtwinkle-sdp) BISON_TARGET(MyParser sdp_parser.yxx ${CMAKE_CURRENT_BINARY_DIR}/sdp_parser.cxx COMPILE_FLAGS "-p yysdp") FLEX_TARGET(MyScanner sdp_scanner.lxx ${CMAKE_CURRENT_BINARY_DIR}/sdp_scanner.cxx COMPILE_FLAGS "-Pyysdp") ADD_FLEX_BISON_DEPENDENCY(MyScanner MyParser) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) set(LIBTWINKLE_SDP-SRCS sdp.cpp sdp_parse_ctrl.cpp ${CMAKE_CURRENT_BINARY_DIR}/sdp_parser.cxx ${CMAKE_CURRENT_BINARY_DIR}/sdp_scanner.cxx sdp_scanner.cxx ) add_library(libtwinkle-sdp OBJECT ${LIBTWINKLE_SDP-SRCS}) twinkle-1.10.1/src/sdp/sdp.cpp000066400000000000000000000471401277565361200161420ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include "protocol.h" #include "sdp_parse_ctrl.h" #include "sdp.h" #include "util.h" #include "parser/hdr_warning.h" #include "parser/parameter.h" #include "audits/memman.h" using namespace std; string sdp_ntwk_type2str(t_sdp_ntwk_type n) { switch(n) { case SDP_NTWK_NULL: return "NULL"; case SDP_NTWK_IN: return "IN"; default: assert(false); } return ""; } t_sdp_ntwk_type str2sdp_ntwk_type(string s) { if (s == "IN") return SDP_NTWK_IN; throw (t_sdp_syntax_error("unknown network type: " + s)); } string sdp_addr_type2str(t_sdp_addr_type a) { switch(a) { case SDP_ADDR_NULL: return "NULL"; case SDP_ADDR_IP4: return "IP4"; case SDP_ADDR_IP6: return "IP6"; default: assert(false); } return ""; } t_sdp_addr_type str2sdp_addr_type(string s) { if (s == "IP4") return SDP_ADDR_IP4; if (s == "IP6") return SDP_ADDR_IP6; throw (t_sdp_syntax_error("unknown address type: " + s)); } string sdp_transport2str(t_sdp_transport t) { switch(t) { case SDP_TRANS_RTP: return "RTP/AVP"; case SDP_TRANS_UDP: return "udp"; default: assert(false); } return ""; } t_sdp_transport str2sdp_transport(string s) { if (s == "RTP/AVP") return SDP_TRANS_RTP; if (s == "udp") return SDP_TRANS_UDP; // Other transports are not recognized and are mapped to other. return SDP_TRANS_OTHER; } t_sdp_media_type str2sdp_media_type(string s) { if (s == "audio") return SDP_AUDIO; if (s == "video") return SDP_VIDEO; return SDP_OTHER; } string sdp_media_type2str(t_sdp_media_type m) { switch(m) { case SDP_AUDIO: return "audio"; case SDP_VIDEO: return "video"; default: assert(false); } return ""; } string get_rtpmap(unsigned format, t_audio_codec codec) { string rtpmap; rtpmap = int2str(format); rtpmap += ' '; switch(codec) { case CODEC_G711_ULAW: rtpmap += SDP_RTPMAP_G711_ULAW; break; case CODEC_G711_ALAW: rtpmap += SDP_RTPMAP_G711_ALAW; break; case CODEC_GSM: rtpmap += SDP_RTPMAP_GSM; break; case CODEC_SPEEX_NB: rtpmap += SDP_RTPMAP_SPEEX_NB; break; case CODEC_SPEEX_WB: rtpmap += SDP_RTPMAP_SPEEX_WB; break; case CODEC_SPEEX_UWB: rtpmap += SDP_RTPMAP_SPEEX_UWB; break; case CODEC_ILBC: rtpmap += SDP_RTPMAP_ILBC; break; case CODEC_G726_16: rtpmap += SDP_RTPMAP_G726_16; break; case CODEC_G726_24: rtpmap += SDP_RTPMAP_G726_24; break; case CODEC_G726_32: rtpmap += SDP_RTPMAP_G726_32; break; case CODEC_G726_40: rtpmap += SDP_RTPMAP_G726_40; break; case CODEC_G729A: rtpmap += SDP_RTPMAP_G729A; break; case CODEC_TELEPHONE_EVENT: rtpmap += SDP_RTPMAP_TELEPHONE_EV; break; default: assert(false); } return rtpmap; } string sdp_media_direction2str(t_sdp_media_direction d) { switch(d) { case SDP_INACTIVE: return "inactive"; case SDP_SENDONLY: return "sendonly"; case SDP_RECVONLY: return "recvonly"; case SDP_SENDRECV: return "sendrecv"; default: assert(false); } return ""; } /////////////////////////////////// // class t_sdp_origin /////////////////////////////////// t_sdp_origin::t_sdp_origin() { network_type = SDP_NTWK_NULL; address_type = SDP_ADDR_NULL; } t_sdp_origin::t_sdp_origin(string _username, string _session_id, string _session_version, string _address) : username(_username), session_id(_session_id), session_version(_session_version), address(_address) { network_type = SDP_NTWK_IN; address_type = SDP_ADDR_IP4; } string t_sdp_origin::encode(void) const { string s; s = "o="; s += username; s += ' ' + session_id; s += ' ' + session_version; s += ' ' + sdp_ntwk_type2str(network_type); s += ' ' + sdp_addr_type2str(address_type); s += ' ' + address; s += CRLF; return s; } /////////////////////////////////// // class t_sdp_connection /////////////////////////////////// t_sdp_connection::t_sdp_connection() { network_type = SDP_NTWK_NULL; } t_sdp_connection::t_sdp_connection(string _address) : address(_address) { network_type = SDP_NTWK_IN; address_type = SDP_ADDR_IP4; } string t_sdp_connection::encode(void) const { string s; s = "c="; s += sdp_ntwk_type2str(network_type); s += ' ' + sdp_addr_type2str(address_type); s += ' ' + address; s += CRLF; return s; } /////////////////////////////////// // class t_sdp_attr /////////////////////////////////// t_sdp_attr::t_sdp_attr(string _name) { name = _name; } t_sdp_attr::t_sdp_attr(string _name, string _value) { name = _name; value = _value; } string t_sdp_attr::encode(void) const { string s; s = "a="; s += name; if (value != "") { s += ':' + value; } s += CRLF; return s; } /////////////////////////////////// // class t_sdp_media /////////////////////////////////// t_sdp_media::t_sdp_media() { port = 0; format_dtmf = 0; } t_sdp_media::t_sdp_media(t_sdp_media_type _media_type, unsigned short _port, const list &_formats, unsigned short _format_dtmf, const map &ac2format) { media_type = sdp_media_type2str(_media_type); port = _port; transport = sdp_transport2str(SDP_TRANS_RTP); format_dtmf = _format_dtmf; for (list::const_iterator i = _formats.begin(); i != _formats.end(); i++) { map::const_iterator it; it = ac2format.find(*i); assert(it != ac2format.end()); add_format(it->second, *i); } if (format_dtmf > 0) { add_format(format_dtmf, CODEC_TELEPHONE_EVENT); } } string t_sdp_media::encode(void) const { string s; s = "m="; s += media_type; s += ' ' + int2str(port); s += ' ' + transport; // Encode media formats. Note that only one of the format lists // will be populated depending on the media type. // Numeric formats. for (list::const_iterator i = formats.begin(); i != formats.end(); ++i) { s += ' ' + int2str(*i); } // Alpha numeric formats. for (list::const_iterator i = alpha_num_formats.begin(); i != alpha_num_formats.end(); ++i) { s += ' ' + *i; } s += CRLF; // Connection information. if (connection.network_type != SDP_NTWK_NULL) { s += connection.encode(); } // Attributes. for (list::const_iterator i = attributes.begin(); i != attributes.end(); ++i) { s += i->encode(); } return s; } void t_sdp_media::add_format(unsigned short f, t_audio_codec codec) { formats.push_back(f); // RFC 3264 5.1 // All media descriptions SHOULD contain an rtpmap string rtpmap = get_rtpmap(f, codec); attributes.push_back(t_sdp_attr("rtpmap", rtpmap)); // RFC 2833 3.9 // Add fmtp parameter if (format_dtmf > 0 && f == format_dtmf) { string fmtp = int2str(f); fmtp += ' '; fmtp += "0-15"; attributes.push_back(t_sdp_attr("fmtp", fmtp)); } else if (codec == CODEC_G729A) { string fmtp = int2str(f); fmtp += ' '; fmtp += "annexb=no"; // annexb=no means G729A attributes.push_back(t_sdp_attr("fmtp", fmtp)); } } t_sdp_attr *t_sdp_media::get_attribute(const string &name) { for (list::iterator i = attributes.begin(); i != attributes.end(); i++) { if (cmp_nocase(i->name, name) == 0) return &(*i); } // Attribute does not exist return NULL; } list t_sdp_media::get_attributes(const string &name) { list l; for (list::iterator i = attributes.begin(); i != attributes.end(); i++) { if (cmp_nocase(i->name, name) == 0) l.push_back(&(*i)); } return l; } t_sdp_media_direction t_sdp_media::get_direction(void) const { t_sdp_attr *a; t_sdp_media *self = const_cast(this); a = self->get_attribute("inactive"); if (a) return SDP_INACTIVE; a = self->get_attribute("sendonly"); if (a) return SDP_SENDONLY; a = self->get_attribute("recvonly"); if (a) return SDP_RECVONLY; return SDP_SENDRECV; } t_sdp_media_type t_sdp_media::get_media_type(void) const { return str2sdp_media_type(media_type); } t_sdp_transport t_sdp_media::get_transport(void) const { return str2sdp_transport(transport); } /////////////////////////////////// // class t_sdp /////////////////////////////////// t_sdp::t_sdp() : t_sip_body(), version(0) {} t_sdp::t_sdp(const string &user, const string &sess_id, const string &sess_version, const string &user_host, const string &media_host, unsigned short media_port, const list &formats, unsigned short format_dtmf, const map &ac2format) : t_sip_body(), version(0), origin(user, sess_id, sess_version, user_host), connection(media_host) { media.push_back(t_sdp_media(SDP_AUDIO, media_port, formats, format_dtmf, ac2format)); } t_sdp::t_sdp(const string &user, const string &sess_id, const string &sess_version, const string &user_host, const string &media_host) : t_sip_body(), version(0), origin(user, sess_id, sess_version, user_host), connection(media_host) {} void t_sdp::add_media(const t_sdp_media &m) { media.push_back(m); } string t_sdp::encode(void) const { string s; s = "v=" + int2str(version) + CRLF; s += origin.encode(); if (session_name == "") { // RFC 3264 5 // Session name may no be empty. Recommende is '-' s += "s=-"; s += CRLF; } else { s += "s=" + session_name + CRLF; } if (connection.network_type != SDP_NTWK_NULL) { s += connection.encode(); } // RFC 3264 5 // Time parameter should be 0 0 s += "t=0 0"; s += CRLF; for (list::const_iterator i = attributes.begin(); i != attributes.end(); i++) { s += i->encode(); } for (list::const_iterator i = media.begin(); i != media.end(); i++) { s += i->encode(); } return s; } t_sip_body *t_sdp::copy(void) const { t_sdp *s = new t_sdp(*this); MEMMAN_NEW(s); return s; } t_body_type t_sdp::get_type(void) const { return BODY_SDP; } t_media t_sdp::get_media(void) const { return t_media("application", "sdp"); } bool t_sdp::is_supported(int &warn_code, string &warn_text) const { warn_text = ""; if (version != 0) { warn_code = W_399_MISCELLANEOUS; warn_text = "SDP version "; warn_text += int2str(version); warn_text += " not supported"; return false; } const t_sdp_media *m = get_first_media(SDP_AUDIO); // Connection information must be present at the session level // and/or the media level if (connection.network_type == SDP_NTWK_NULL) { if (m == NULL || m->connection.network_type == SDP_NTWK_NULL) { warn_code = W_399_MISCELLANEOUS; warn_text = "c-line missing"; return false; } } else { // Only Internet is supported if (connection.network_type != SDP_NTWK_IN) { warn_code = W_300_INCOMPATIBLE_NWK_PROT; return false; } // Only IPv4 is supported if (connection.address_type != SDP_ADDR_IP4) { warn_code = W_301_INCOMPATIBLE_ADDR_FORMAT; return false; } } // There must be at least 1 audio stream with a non-zero port value if (m == NULL && !media.empty()) { warn_code = W_304_MEDIA_TYPE_NOT_AVAILABLE; warn_text = "Valid media stream for audio is missing"; return false; } // RFC 3264 5, RFC 3725 flow IV // There may be 0 media streams if (media.empty()) { return true; } // Check connection information on media level if (m->connection.network_type != SDP_NTWK_NULL && m->connection.address_type != SDP_ADDR_IP4) { warn_code = W_301_INCOMPATIBLE_ADDR_FORMAT; return false; } if (m->get_transport() != SDP_TRANS_RTP) { warn_code = W_302_INCOMPATIBLE_TRANS_PROT; return false; } t_sdp_media *m2 = const_cast(m); const t_sdp_attr *a = m2->get_attribute("ptime"); if (a) { unsigned short p = atoi(a->value.c_str()); if (p < MIN_PTIME) { warn_code = W_306_ATTRIBUTE_NOT_UNDERSTOOD; warn_text = "Attribute 'ptime' too small. must be >= "; warn_text += int2str(MIN_PTIME); return false; } if (p > MAX_PTIME) { warn_code = W_306_ATTRIBUTE_NOT_UNDERSTOOD; warn_text = "Attribute 'ptime' too big. must be <= "; warn_text += int2str(MAX_PTIME); return false; } } return true; } string t_sdp::get_rtp_host(t_sdp_media_type media_type) const { const t_sdp_media *m = get_first_media(media_type); assert(m != NULL); // If the media line has its own connection information, then // take the host information from there. if (m->connection.network_type == SDP_NTWK_IN) { return m->connection.address; } // The host information must be in the session connection info assert(connection.network_type == SDP_NTWK_IN); return connection.address; } unsigned short t_sdp::get_rtp_port(t_sdp_media_type media_type) const { const t_sdp_media *m = get_first_media(media_type); assert(m != NULL); return m->port; } list t_sdp::get_codecs(t_sdp_media_type media_type) const { const t_sdp_media *m = get_first_media(media_type); assert(m != NULL); return m->formats; } string t_sdp::get_codec_description(t_sdp_media_type media_type, unsigned short codec) const { t_sdp_media *m = const_cast(get_first_media(media_type)); assert(m != NULL); const list attrs = m->get_attributes("rtpmap"); if (attrs.empty()) return ""; for (list::const_iterator i = attrs.begin(); i != attrs.end(); i++) { vector l = split_ws((*i)->value); if (atoi(l.front().c_str()) == codec) { return l.back(); } } return ""; } t_audio_codec t_sdp::get_rtpmap_codec(const string &rtpmap) const { if (rtpmap.empty()) return CODEC_NULL; vector rtpmap_elems = split(rtpmap, '/'); if (rtpmap_elems.size() < 2) { // RFC 2327 // The rtpmap should at least contain the encoding name // and sample rate return CODEC_UNSUPPORTED; } string codec_name = trim(rtpmap_elems[0]); int sample_rate = atoi(trim(rtpmap_elems[1]).c_str()); if (cmp_nocase(codec_name, SDP_AC_NAME_G711_ULAW) == 0 && sample_rate == 8000) { return CODEC_G711_ULAW; } else if (cmp_nocase(codec_name, SDP_AC_NAME_G711_ALAW) == 0 && sample_rate == 8000) { return CODEC_G711_ALAW; } else if (cmp_nocase(codec_name, SDP_AC_NAME_GSM) == 0 && sample_rate == 8000) { return CODEC_GSM; } else if (cmp_nocase(codec_name, SDP_AC_NAME_SPEEX) == 0 && sample_rate == 8000) { return CODEC_SPEEX_NB; } else if (cmp_nocase(codec_name, SDP_AC_NAME_SPEEX) == 0 && sample_rate == 16000) { return CODEC_SPEEX_WB; } else if (cmp_nocase(codec_name, SDP_AC_NAME_SPEEX) == 0 && sample_rate == 32000) { return CODEC_SPEEX_UWB; } else if (cmp_nocase(codec_name, SDP_AC_NAME_ILBC) == 0 && sample_rate == 8000) { return CODEC_ILBC; } else if (cmp_nocase(codec_name, SDP_AC_NAME_G726_16) == 0 && sample_rate == 8000) { return CODEC_G726_16; } else if (cmp_nocase(codec_name, SDP_AC_NAME_G726_24) == 0 && sample_rate == 8000) { return CODEC_G726_24; } else if (cmp_nocase(codec_name, SDP_AC_NAME_G726_32) == 0 && sample_rate == 8000) { return CODEC_G726_32; } else if (cmp_nocase(codec_name, SDP_AC_NAME_G726_40) == 0 && sample_rate == 8000) { return CODEC_G726_40; } else if (cmp_nocase(codec_name, SDP_AC_NAME_G729) == 0 && sample_rate == 8000) { return CODEC_G729A; } else if (cmp_nocase(codec_name, SDP_AC_NAME_TELEPHONE_EV) == 0) { return CODEC_TELEPHONE_EVENT; } return CODEC_UNSUPPORTED; } t_audio_codec t_sdp::get_codec(t_sdp_media_type media_type, unsigned short codec) const { string rtpmap = get_codec_description(media_type, codec); // If there is no rtpmap description then use the static // payload definition as defined by RFC 3551 if (rtpmap.empty()) { switch(codec) { case SDP_FORMAT_G711_ULAW: return CODEC_G711_ULAW; case SDP_FORMAT_G711_ALAW: return CODEC_G711_ALAW; case SDP_FORMAT_GSM: return CODEC_GSM; default: return CODEC_UNSUPPORTED; } } // Use the rtpmap description to map the payload number // to a codec return get_rtpmap_codec(rtpmap); } t_sdp_media_direction t_sdp::get_direction(t_sdp_media_type media_type) const { const t_sdp_media *m = get_first_media(media_type); assert(m != NULL); return m->get_direction(); } string t_sdp::get_fmtp(t_sdp_media_type media_type, unsigned short codec) const { t_sdp_media *m = const_cast(get_first_media(media_type)); assert(m != NULL); const list attrs = m->get_attributes("fmtp"); if (attrs.empty()) return ""; for (list::const_iterator i = attrs.begin(); i != attrs.end(); i++) { vector l = split_ws((*i)->value); if (atoi(l.front().c_str()) == codec) { return l.back(); } } return ""; } int t_sdp::get_fmtp_int_param(t_sdp_media_type media_type, unsigned short codec, const string param) const { string fmtp = get_fmtp(media_type, codec); if (fmtp.empty()) return -1; int value; list l = str2param_list(fmtp); list::const_iterator it = find(l.begin(), l.end(), t_parameter(param, "")); if (it != l.end()) { value = atoi(it->value.c_str()); } else { value = -1; } return value; } unsigned short t_sdp::get_ptime(t_sdp_media_type media_type) const { t_sdp_media *m = const_cast(get_first_media(media_type)); assert(m != NULL); const t_sdp_attr *a = m->get_attribute("ptime"); if (!a) return 0; return atoi(a->value.c_str()); } bool t_sdp::get_zrtp_support(t_sdp_media_type media_type) const { t_sdp_media *m = const_cast(get_first_media(media_type)); assert(m != NULL); const t_sdp_attr *a = m->get_attribute("zrtp"); if (!a) return false; return true; } void t_sdp::set_ptime(t_sdp_media_type media_type, unsigned short ptime) { t_sdp_media *m = const_cast(get_first_media(media_type)); assert(m != NULL); t_sdp_attr a("ptime", int2str(ptime)); m->attributes.push_back(a); } void t_sdp::set_direction(t_sdp_media_type media_type, t_sdp_media_direction direction) { t_sdp_media *m = const_cast(get_first_media(media_type)); assert(m != NULL); t_sdp_attr a(sdp_media_direction2str(direction)); m->attributes.push_back(a); } void t_sdp::set_fmtp(t_sdp_media_type media_type, unsigned short codec, const string &fmtp) { t_sdp_media *m = const_cast(get_first_media(media_type)); assert(m != NULL); string s = int2str(codec); s += ' '; s += fmtp; t_sdp_attr a("fmtp", s); m->attributes.push_back(a); } void t_sdp::set_fmtp_int_param(t_sdp_media_type media_type, unsigned short codec, const string ¶m, int value) { string fmtp(param); fmtp += '='; fmtp += int2str(value); set_fmtp(media_type, codec, fmtp); } void t_sdp::set_zrtp_support(t_sdp_media_type media_type) { t_sdp_media *m = const_cast(get_first_media(media_type)); assert(m != NULL); t_sdp_attr a("zrtp"); m->attributes.push_back(a); } const t_sdp_media *t_sdp::get_first_media(t_sdp_media_type media_type) const { for (list::const_iterator i = media.begin(); i != media.end(); i++) { if (i->get_media_type() == media_type && i->port != 0) { return &(*i); } } return NULL; } bool t_sdp::local_ip_check(void) const { if (origin.address == AUTO_IP4_ADDRESS) return false; if (connection.address == AUTO_IP4_ADDRESS) return false; for (list::const_iterator it = media.begin(); it != media.end(); ++it) { if (it->connection.address == AUTO_IP4_ADDRESS) return false; } return true; } twinkle-1.10.1/src/sdp/sdp.h000066400000000000000000000205651277565361200156110ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Session description #ifndef _H_SDP #define _H_SDP #include #include #include #include "audio/audio_codecs.h" #include "parser/sip_body.h" /** User name to be put in o= line of SDP */ #define SDP_O_USER "twinkle" // Audio codec formats #define SDP_FORMAT_G711_ULAW 0 #define SDP_FORMAT_GSM 3 #define SDP_FORMAT_G711_ALAW 8 #define SDP_FORMAT_G729 18 // rtpmap values #define SDP_RTPMAP_G711_ULAW "PCMU/8000" #define SDP_RTPMAP_GSM "GSM/8000" #define SDP_RTPMAP_G711_ALAW "PCMA/8000" #define SDP_RTPMAP_SPEEX_NB "speex/8000" #define SDP_RTPMAP_SPEEX_WB "speex/16000" #define SDP_RTPMAP_SPEEX_UWB "speex/32000" #define SDP_RTPMAP_ILBC "iLBC/8000" #define SDP_RTPMAP_G726_16 "G726-16/8000" #define SDP_RTPMAP_G726_24 "G726-24/8000" #define SDP_RTPMAP_G726_32 "G726-32/8000" #define SDP_RTPMAP_G726_40 "G726-40/8000" #define SDP_RTPMAP_G729A "G729/8000" #define SDP_RTPMAP_TELEPHONE_EV "telephone-event/8000" // Audio codec names #define SDP_AC_NAME_G711_ULAW "PCMU" #define SDP_AC_NAME_G711_ALAW "PCMA" #define SDP_AC_NAME_GSM "GSM" #define SDP_AC_NAME_SPEEX "speex" #define SDP_AC_NAME_ILBC "iLBC" #define SDP_AC_NAME_G726_16 "G726-16" #define SDP_AC_NAME_G726_24 "G726-24" #define SDP_AC_NAME_G726_32 "G726-32" #define SDP_AC_NAME_G726_40 "G726-40" #define SDP_AC_NAME_G729 "G729" #define SDP_AC_NAME_TELEPHONE_EV "telephone-event" // Check on fmtp parameter values #define VALID_ILBC_MODE(mode) ((mode) == 20 || (mode == 30)) using namespace std; enum t_sdp_ntwk_type { SDP_NTWK_NULL, SDP_NTWK_IN }; string sdp_ntwk_type2str(t_sdp_ntwk_type n); t_sdp_ntwk_type str2sdp_ntwk_type(string s); enum t_sdp_addr_type { SDP_ADDR_NULL, SDP_ADDR_IP4, SDP_ADDR_IP6 }; string sdp_addr_type2str(t_sdp_addr_type a); t_sdp_addr_type str2sdp_addr_type(string s); /** Transport protocol */ enum t_sdp_transport { SDP_TRANS_RTP, /**< RTP/AVP */ SDP_TRANS_UDP, /**< UDP */ SDP_TRANS_OTHER /**< Another protocol not yet supported */ }; string sdp_transport2str(t_sdp_transport t); t_sdp_transport str2sdp_transport(string s); enum t_sdp_media_direction { SDP_INACTIVE, SDP_SENDONLY, SDP_RECVONLY, SDP_SENDRECV }; string sdp_media_direction2str(t_sdp_media_direction d); enum t_sdp_media_type { SDP_AUDIO, SDP_VIDEO, SDP_OTHER }; t_sdp_media_type str2sdp_media_type(string s); string sdp_media_type2str(t_sdp_media_type m); class t_sdp_origin { public: string username; string session_id; string session_version; t_sdp_ntwk_type network_type; t_sdp_addr_type address_type; string address; t_sdp_origin(); t_sdp_origin(string _username, string _session_id, string _session_version, string _address); string encode(void) const; }; class t_sdp_connection { public: t_sdp_ntwk_type network_type; t_sdp_addr_type address_type; string address; t_sdp_connection(); t_sdp_connection(string _address); string encode(void) const; }; class t_sdp_attr { public: string name; string value; t_sdp_attr(string _name); t_sdp_attr(string _name, string _value); string encode(void) const; }; /** * Media definition. * The data from an m= line and associated a= lines. */ class t_sdp_media { private: /** Dynamic payload type for DTMF */ unsigned short format_dtmf; public: /** The media type, e.g. audio, video */ string media_type; /** Port to receive media */ unsigned short port; /** Transport protocol, e.g. RTP/AVP */ string transport; /** * @name Media formats * Depending on the media type, formats are in numeric format or * alpha numeric format. Only one of the following formats will * be populated. */ //@{ /** Media formats in numeric form, i.e. audio codecs */ list formats; /** Media formats in alpha numeric form. */ list alpha_num_formats; //@} /** Optional connection information if not specified on global level. */ t_sdp_connection connection; /** Attributes (a= lines) */ list attributes; t_sdp_media(); t_sdp_media(t_sdp_media_type _media_type, unsigned short _port, const list &_formats, unsigned short _format_dtmf, const map &ac2format); string encode(void) const; void add_format(unsigned short f, t_audio_codec codec); t_sdp_attr *get_attribute(const string &name); listget_attributes(const string &name); t_sdp_media_direction get_direction(void) const; t_sdp_media_type get_media_type(void) const; t_sdp_transport get_transport(void) const; }; class t_sdp : public t_sip_body { public: unsigned short version; t_sdp_origin origin; string session_name; t_sdp_connection connection; list attributes; list media; t_sdp(); // Create SDP with a single audio media stream t_sdp(const string &user, const string &sess_id, const string &sess_version, const string &user_host, const string &media_host, unsigned short media_port, const list &formats, unsigned short format_dtmf, const map &ac2format); // Create SDP without media streams t_sdp(const string &user, const string &sess_id, const string &sess_version, const string &user_host, const string &media_host); // Add media stream void add_media(const t_sdp_media &m); string encode(void) const; t_sip_body *copy(void) const; t_body_type get_type(void) const; t_media get_media(void) const; // Return true if the current SDP is supported: // version is 0 // 1 audio stream RTP // IN IP4 addressing // connection at session level only // If false is returned, then a warning code and text is returned. bool is_supported(int &warn_code, string &warn_text) const; // Get/set codec/rtp info for first media stream having a non-zero // value for the port of the given media type string get_rtp_host(t_sdp_media_type media_type) const; unsigned short get_rtp_port(t_sdp_media_type media_type) const; list get_codecs(t_sdp_media_type media_type) const; // Get codec description from rtpmap string get_codec_description(t_sdp_media_type media_type, unsigned short codec) const; t_audio_codec get_rtpmap_codec(const string &rtpmap) const; t_audio_codec get_codec(t_sdp_media_type media_type, unsigned short codec) const; t_sdp_media_direction get_direction(t_sdp_media_type media_type) const; // Get ftmp attribute string get_fmtp(t_sdp_media_type media_type, unsigned short codec) const; // Get a specific parameter from fmtp, assuming the fmtp string is a list // of paramter=value strings separated by semi-colons // Returns -1 on failure int get_fmtp_int_param(t_sdp_media_type media_type, unsigned short codec, const string param) const; // Get ptime. Returns 0 if ptime is not present unsigned short get_ptime(t_sdp_media_type media_type) const; bool get_zrtp_support(t_sdp_media_type media_type) const; void set_ptime(t_sdp_media_type media_type, unsigned short ptime); void set_direction(t_sdp_media_type media_type, t_sdp_media_direction direction); void set_fmtp(t_sdp_media_type media_type, unsigned short codec, const string &fmtp); void set_fmtp_int_param(t_sdp_media_type media_type, unsigned short codec, const string ¶m, int value); void set_zrtp_support(t_sdp_media_type media_type); // Returns a pointer to the first media stream in the list of media // streams having a non-zero port value for the give media type. // Returns NULL if no such media stream can be found. const t_sdp_media *get_first_media(t_sdp_media_type media_type) const; /** * Check if all local IP address are correctly filled in. This * check is an integrity check to help debugging the auto IP * discover feature. */ virtual bool local_ip_check(void) const; }; #endif twinkle-1.10.1/src/sdp/sdp_parse_ctrl.cpp000066400000000000000000000036361277565361200203620ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "sdp_parse_ctrl.h" #include "audits/memman.h" // Interface to Bison extern int yysdpparse(void); // Interface to Flex struct yy_buffer_state; extern struct yy_buffer_state *yysdp_scan_string(const char *); extern void yysdp_delete_buffer(struct yy_buffer_state *); t_mutex t_sdp_parser::mtx_parser; t_sdp_parser::t_context t_sdp_parser::context = t_sdp_parser::X_INITIAL; t_sdp *t_sdp_parser::sdp = NULL; t_sdp *t_sdp_parser::parse(const string &s) { int ret; struct yy_buffer_state *b; t_mutex_guard guard(mtx_parser); sdp = new t_sdp(); MEMMAN_NEW(sdp); // The SDP body should end with a CRLF. Some implementations // do not send this last CRLF. Allow this deviation by adding // the last CRLF if it is missing. char last_char = s.at(s.size()-1); if (last_char == '\n' || last_char == '\r') { // The SDP parser allows \r, \r\n and \n as CRLF b = yysdp_scan_string(s.c_str()); } else { // Last CRLF is missing. b = yysdp_scan_string((s + "\r\n").c_str()); } ret = yysdpparse(); yysdp_delete_buffer(b); if (ret != 0) { MEMMAN_DELETE(sdp); delete sdp; sdp = NULL; throw ret; } return sdp; } t_sdp_syntax_error::t_sdp_syntax_error(const string &e) { error = e; } twinkle-1.10.1/src/sdp/sdp_parse_ctrl.h000066400000000000000000000034761277565361200200310ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Parser control #ifndef _SDP_PARSE_CTRL_H #define _SDP_PARSE_CTRL_H #include "sdp.h" #include "threads/mutex.h" #define SDP t_sdp_parser::sdp #define CTX_INITIAL (t_sdp_parser::context = t_sdp_parser::X_INITIAL) #define CTX_SAFE (t_sdp_parser::context = t_sdp_parser::X_SAFE) #define CTX_NUM (t_sdp_parser::context = t_sdp_parser::X_NUM) #define CTX_LINE (t_sdp_parser::context = t_sdp_parser::X_LINE) // The t_sdp_parser controls the direction of the scanner/parser // process and it stores the results from the parser. class t_sdp_parser { private: /** Mutex to synchronize parse operations */ static t_mutex mtx_parser; public: enum t_context { X_INITIAL, // Initial context X_SAFE, // Safe context X_NUM, // Number context X_LINE, // Whole line context }; static t_context context; // Scan context static t_sdp *sdp; // SDP that has been parsed // Parse string s. Throw int exception when parsing fails. static t_sdp *parse(const string &s); }; // Error that can be thrown as exception class t_sdp_syntax_error { public: string error; t_sdp_syntax_error(const string &e); }; #endif twinkle-1.10.1/src/sdp/sdp_parser.h000066400000000000000000000056551277565361200171700ustar00rootroot00000000000000/* A Bison parser, made by GNU Bison 2.3. */ /* Skeleton interface for Bison's Yacc-like parsers in C Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. 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, 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, see . */ /* As a special exception, you may create a larger work that contains part or all of the Bison parser skeleton and distribute that work under terms of your choice, so long as that work isn't itself a parser generator using the skeleton or a modified version thereof as a parser skeleton. Alternatively, if you modify or redistribute the parser skeleton itself, you may (at your option) remove this special exception, which will cause the skeleton and the resulting Bison output files to be licensed under the GNU General Public License without this special exception. This special exception was added by the Free Software Foundation in version 2.2 of Bison. */ /* Tokens. */ #ifndef YYTOKENTYPE # define YYTOKENTYPE /* Put the tokens into the symbol table, so that GDB and other debuggers know about them. */ enum yytokentype { T_NUM = 258, T_TOKEN = 259, T_SAFE = 260, T_LINE = 261, T_CRLF = 262, T_LINE_VERSION = 263, T_LINE_ORIGIN = 264, T_LINE_SESSION_NAME = 265, T_LINE_CONNECTION = 266, T_LINE_ATTRIBUTE = 267, T_LINE_MEDIA = 268, T_LINE_UNKNOWN = 269, T_NULL = 270 }; #endif /* Tokens. */ #define T_NUM 258 #define T_TOKEN 259 #define T_SAFE 260 #define T_LINE 261 #define T_CRLF 262 #define T_LINE_VERSION 263 #define T_LINE_ORIGIN 264 #define T_LINE_SESSION_NAME 265 #define T_LINE_CONNECTION 266 #define T_LINE_ATTRIBUTE 267 #define T_LINE_MEDIA 268 #define T_LINE_UNKNOWN 269 #define T_NULL 270 #if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED typedef union YYSTYPE #line 50 "sdp_parser.yxx" { int yysdpt_int; string *yysdpt_str; t_sdp_ntwk_type yysdpt_ntwk_type; t_sdp_addr_type yysdpt_addr_type; t_sdp_connection *yysdpt_connection; list *yysdpt_attributes; t_sdp_attr *yysdpt_attribute; t_sdp_media *yysdpt_media; list *yysdpt_token_list; } /* Line 1529 of yacc.c. */ #line 91 "sdp_parser.h" YYSTYPE; # define yystype YYSTYPE /* obsolescent; will be withdrawn */ # define YYSTYPE_IS_DECLARED 1 # define YYSTYPE_IS_TRIVIAL 1 #endif extern YYSTYPE yysdplval; twinkle-1.10.1/src/sdp/sdp_parser.yxx000066400000000000000000000171071277565361200175640ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ %{ #include #include #include #include "sdp_parse_ctrl.h" #include "sdp.h" #include "util.h" #include "audits/memman.h" using namespace std; extern int yysdplex(void); void yysdperror(const char *s); %} // The %debug option causes a problem with the %destructor options later on. // The bison compilor generates undefined symbols: // // parser.y: In function `void yysymprint(FILE*, int, yystype)': // parser.y:0: error: `null' undeclared (first use this function) // // So if you need to debug, then outcomment the %destructor first. This will // do no harm to your debugging, it only will cause memory leaks during // error handling. // // %debug %expect 2 /* See below for the expected shift/reduce conflicts. */ %union { int yysdpt_int; string *yysdpt_str; t_sdp_ntwk_type yysdpt_ntwk_type; t_sdp_addr_type yysdpt_addr_type; t_sdp_connection *yysdpt_connection; list *yysdpt_attributes; t_sdp_attr *yysdpt_attribute; t_sdp_media *yysdpt_media; list *yysdpt_token_list; } %token T_NUM %token T_TOKEN %token T_SAFE %token T_LINE %token T_CRLF %token T_LINE_VERSION %token T_LINE_ORIGIN %token T_LINE_SESSION_NAME %token T_LINE_CONNECTION %token T_LINE_ATTRIBUTE %token T_LINE_MEDIA %token T_LINE_UNKNOWN // The token T_NULL is never returned by the scanner. %token T_NULL %destructor { MEMMAN_DELETE($$); delete $$; } T_TOKEN %destructor { MEMMAN_DELETE($$); delete $$; } T_SAFE %destructor { MEMMAN_DELETE($$); delete $$; } T_LINE %type address_type %type connection %type network_type %type attributes %type attribute %type attribute2 %type media %type transport %type formats %destructor { MEMMAN_DELETE($$); delete $$; } connection %destructor { MEMMAN_DELETE($$); delete $$; } attributes %destructor { MEMMAN_DELETE($$); delete $$; } attribute %destructor { MEMMAN_DELETE($$); delete $$; } attribute2 %destructor { MEMMAN_DELETE($$); delete $$; } media %destructor { MEMMAN_DELETE($$); delete $$; } transport %destructor { MEMMAN_DELETE($$); delete $$; } formats %% /* The unknown_lines cause an expected shift/reduce conflict */ sdp_body: version origin session_name unknown_lines sess_connection unknown_lines sess_attributes media_list { /* Parsing stops here. Remaining text is * not parsed. */ YYACCEPT; } | error T_NULL { /* KLUDGE to avoid memory leak in bison. * See the SIP parser for an explanation. */ YYABORT; } ; version: T_LINE_VERSION { CTX_NUM; } T_NUM { CTX_INITIAL; } T_CRLF { SDP->version = $3; } ; origin: T_LINE_ORIGIN { CTX_SAFE; } T_SAFE { CTX_INITIAL; } T_TOKEN T_TOKEN network_type address_type T_TOKEN T_CRLF { SDP->origin.username = *$3; SDP->origin.session_id = *$5; SDP->origin.session_version = *$6; SDP->origin.network_type = $7; SDP->origin.address_type = $8; SDP->origin.address = *$9; MEMMAN_DELETE($3); delete $3; MEMMAN_DELETE($5); delete $5; MEMMAN_DELETE($6); delete $6; MEMMAN_DELETE($9); delete $9; } ; network_type: T_TOKEN { try { $$ = str2sdp_ntwk_type(*$1); MEMMAN_DELETE($1); delete $1; } catch (t_sdp_syntax_error) { // Invalid network type. // Set network type to NULL. This way the message // will not be discarded and the error can be // handled on the SIP level (error response or // call tear down). MEMMAN_DELETE($1); delete $1; $$ = SDP_NTWK_NULL; } } ; address_type: T_TOKEN { try { $$ = str2sdp_addr_type(*$1); MEMMAN_DELETE($1); delete $1; } catch (t_sdp_syntax_error) { // Invalid address type MEMMAN_DELETE($1); delete $1; $$ = SDP_ADDR_NULL; } } ; session_name: T_LINE_SESSION_NAME { CTX_LINE; } T_LINE { CTX_INITIAL; } T_CRLF { SDP->session_name = *$3; MEMMAN_DELETE($3); delete $3; } ; sess_connection: connection { SDP->connection = *$1; MEMMAN_DELETE($1); delete $1; } ; connection: /* empty */ { $$ = new t_sdp_connection(); MEMMAN_NEW($$); } | T_LINE_CONNECTION network_type address_type T_TOKEN T_CRLF { $$ = new t_sdp_connection(); MEMMAN_NEW($$); $$->network_type = $2; $$->address_type = $3; $$->address = *$4; MEMMAN_DELETE($4); delete $4; } ; sess_attributes: attributes { SDP->attributes = *$1; MEMMAN_DELETE($1); delete $1; } ; attributes: /* emtpy */ { $$ = new list; MEMMAN_NEW($$); } | attributes attribute { $$->push_back(*$2); MEMMAN_DELETE($2); delete $2; } ; attribute: T_LINE_ATTRIBUTE attribute2 T_CRLF { $$ = $2; } ; attribute2: T_TOKEN { $$ = new t_sdp_attr(*$1); MEMMAN_NEW($$); MEMMAN_DELETE($1); delete $1; } | T_TOKEN ':' { CTX_LINE; } T_LINE { CTX_INITIAL; } { $$ = new t_sdp_attr(*$1, *$4); MEMMAN_NEW($$); MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($4); delete $4; } ; media_list: /* empty */ | media_list media { SDP->media.push_back(*$2); MEMMAN_DELETE($2); delete $2; } ; /* The unknown_lines cause an expected shift/reduce conflict */ media: T_LINE_MEDIA T_TOKEN { CTX_NUM; } T_NUM { CTX_INITIAL; } transport formats T_CRLF unknown_lines connection unknown_lines attributes { $$ = new t_sdp_media(); MEMMAN_NEW($$); if ($4 > 65535) YYERROR; $$->media_type = tolower(*$2); $$->port = $4; $$->transport = *$6; /* The format depends on the media type */ switch($$->get_media_type()) { case SDP_AUDIO: case SDP_VIDEO: /* Numeric format */ for (list::const_iterator it = $7->begin(); it != $7->end(); ++it) { if (is_number(*it)) $$->formats.push_back(atoi(it->c_str())); } break; default: /* Alpha numeric format */ $$->alpha_num_formats = *$7; } $$->connection = *$10; $$->attributes = *$12; MEMMAN_DELETE($2); delete $2; MEMMAN_DELETE($6); delete $6; MEMMAN_DELETE($7); delete $7; MEMMAN_DELETE($10); delete $10; MEMMAN_DELETE($12); delete $12; } ; transport: T_TOKEN { $$ = $1; } | T_TOKEN '/' T_TOKEN { $$ = new string(*$1 + '/' + *$3); MEMMAN_NEW($$); MEMMAN_DELETE($1); delete $1; MEMMAN_DELETE($3); delete $3; } // For RTP/AVP a format is a number. For other transport protocols, // non-numerical formats are possible. formats: /* empty */ { $$ = new list; MEMMAN_NEW($$); } | formats T_TOKEN { $$->push_back(*$2); MEMMAN_DELETE($2); delete $2; } ; /* Skip unknown lines */ unknown_lines: /* empty */ | unknown_lines unknown_line ; unknown_line: T_LINE_UNKNOWN { CTX_LINE; } T_LINE { CTX_INITIAL; } T_CRLF { MEMMAN_DELETE($3); delete $3; } ; %% void yysdperror (const char *s) /* Called by yysdpparse on error */ { // printf ("%s\n", s); } twinkle-1.10.1/src/sdp/sdp_scanner.lxx000066400000000000000000000051111277565361200176740ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ %{ #include #include "sdp_parse_ctrl.h" #include "sdp_parser.h" #include "audits/memman.h" using namespace std; %} %option noyywrap %option stack DIGIT [0-9] ALPHA [a-zA-Z] ALNUM [a-zA-Z0-9] TOKEN_SYM [[:alnum:]\-\.!%\*_\+\'~] SAFE_SYM [[:alnum:]'`\-\.\/:\?\"#\$&\*;=@\[\]\^_\{|\}\+~\"] %x C_NUM %x C_LINE %x C_SAFE %% switch (t_sdp_parser::context) { case t_sdp_parser::X_NUM: BEGIN(C_NUM); break; case t_sdp_parser::X_LINE: BEGIN(C_LINE); break; case t_sdp_parser::X_SAFE: BEGIN(C_SAFE); break; default: BEGIN(INITIAL); } /* SDP lines */ ^v= { return T_LINE_VERSION; } ^o= { return T_LINE_ORIGIN; } ^s= { return T_LINE_SESSION_NAME; } ^c= { return T_LINE_CONNECTION; } ^a= { return T_LINE_ATTRIBUTE; } ^m= { return T_LINE_MEDIA; } ^{ALPHA}= { return T_LINE_UNKNOWN; } /* Token as define in RFC 3261 */ {TOKEN_SYM}+ { yysdplval.yysdpt_str = new string(yysdptext); MEMMAN_NEW(yysdplval.yysdpt_str); return T_TOKEN; } /* End of line */ \r\n { return T_CRLF; } \n { return T_CRLF; } [[:blank:]] /* Skip white space */ /* Single character token */ . { return yysdptext[0]; } /* Number */ {DIGIT}+ { yysdplval.yysdpt_int = atoi(yysdptext); return T_NUM; } [[:blank:]] /* Skip white space */ . { return yysdptext[0]; } \r\n { return T_CRLF; } \n { return T_CRLF; } /* Safe */ {SAFE_SYM}+ { yysdplval.yysdpt_str = new string(yysdptext); MEMMAN_NEW(yysdplval.yysdpt_str); return T_SAFE; } [[:blank:]] /* Skip white space */ . { return yysdptext[0]; } \r\n { return T_CRLF; } \n { return T_CRLF; } /* Get all text till end of line */ [^\r\n]+ { yysdplval.yysdpt_str = new string(yysdptext); MEMMAN_NEW(yysdplval.yysdpt_str); return T_LINE; } \r\n { return T_CRLF; } \n { return T_CRLF; } \r { return T_CRLF; } twinkle-1.10.1/src/sender.cpp000066400000000000000000000373101277565361200160440ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include #include #include #include #include #include "events.h" #include "log.h" #include "phone.h" #include "sender.h" #include "translator.h" #include "userintf.h" #include "util.h" #include "sockets/connection_table.h" #include "sockets/socket.h" #include "parser/parse_ctrl.h" #include "parser/sip_message.h" #include "audits/memman.h" #include "stun/stun.h" #define MAX_TRANSMIT_RETRIES 3 // Maximum size of a message to be sent over an existing connection. // For larger message always a new connection is opened to avoid // head of line blocking. #define MAX_REUSE_CONN_SIZE 10240 extern t_socket_udp *sip_socket; extern t_connection_table *connection_table; extern t_event_queue *evq_sender; extern t_event_queue *evq_trans_mgr; extern t_event_queue *evq_trans_layer; extern t_phone *phone; // Number of consecutive non-icmp errors received static int num_non_icmp_errors = 0; // Check if the error is caused by an incoming ICMP error. If so, then deliver // the ICMP error to the transaction manager. // // err - error returned by sendto // dst_addr - destination IP address of packet that failed to be sent // dst_port - destination port of packet that failed to be sent // // Returns true if the packet that failed to be sent, should still be sent. // Returns false if the packet that failed to be sent, should be discarded. static bool handle_socket_err(int err, unsigned long dst_addr, unsigned short dst_port) { string log_msg; // Check if an ICMP error has been received t_icmp_msg icmp; if (sip_socket->get_icmp(icmp)) { log_msg = "Received ICMP from: "; log_msg += h_ip2str(icmp.icmp_src_ipaddr); log_msg += "\nICMP type: "; log_msg += int2str(icmp.type); log_msg += "\nICMP code: "; log_msg += int2str(icmp.code); log_msg += "\nDestination of packet causing ICMP: "; log_msg += h_ip2str(icmp.ipaddr); log_msg += ":"; log_msg += int2str(icmp.port); log_msg += "\nSocket error: "; log_msg += int2str(err); log_msg += " "; log_msg += get_error_str(err); log_file->write_report(log_msg, "::hanlde_socket_err", LOG_NORMAL); evq_trans_mgr->push_icmp(icmp); num_non_icmp_errors = 0; // If the ICMP error comes from the same destination as the // destination of the packet that failed to be sent, then the // packet should be discarded as it can most likely not be // delivered and would cause an infinite loop of ICMP errors // otherwise. if (icmp.ipaddr == dst_addr && icmp.port == dst_port) { return false; } } else { // Even if an ICMP message is received this code can get executed. // Sometimes the error is already present on the socket, but the ICMP // message is not yet queued. log_msg = "Failed to send to SIP UDP socket.\n"; log_msg += "Error code: "; log_msg += int2str(err); log_msg += "\n"; log_msg += get_error_str(err); log_file->write_report(log_msg, "::handle_socket_err"); num_non_icmp_errors++; /* * non-ICMP errors occur when a destination on the same * subnet cannot be reached. So this code seems to be * harmful. if (num_non_icmp_errors > 100) { log_msg = "Excessive number of socket errors."; log_file->write_report(log_msg, "::handle_socket_err", LOG_NORMAL, LOG_CRITICAL); log_msg = TRANSLATE("Excessive number of socket errors."); ui->cb_show_msg(log_msg, MSG_CRITICAL); exit(1); } */ } return true; } static void send_sip_udp(t_event *event) { t_event_network *e; e = (t_event_network *)event; assert(e->dst_addr != 0); assert(e->dst_port != 0); // Set correct transport in topmost Via header of a request. // For a response the Via header is copied from the incoming request. t_sip_message *sip_msg = e->get_msg(); if (sip_msg->get_type() == MSG_REQUEST) { sip_msg->hdr_via.via_list.front().transport = "UDP"; } string m = sip_msg->encode(); log_file->write_header("::send_sip_udp", LOG_SIP); log_file->write_raw("Send to: udp:"); log_file->write_raw(h_ip2str(e->dst_addr)); log_file->write_raw(":"); log_file->write_raw(e->dst_port); log_file->write_endl(); log_file->write_raw(m); log_file->write_endl(); log_file->write_footer(); bool msg_sent = false; int transmit_count = 0; while (!msg_sent && transmit_count++ <= MAX_TRANSMIT_RETRIES) { try { sip_socket->sendto(e->dst_addr, e->dst_port, m.c_str(), m.size()); num_non_icmp_errors = 0; msg_sent = true; } catch (int err) { if (!handle_socket_err(err, e->dst_addr, e->dst_port)) { // Discard packet. msg_sent = true; } else { if (transmit_count <= MAX_TRANSMIT_RETRIES) { // Sleep 100 ms struct timespec sleeptimer; sleeptimer.tv_sec = 0; sleeptimer.tv_nsec = 100000000; nanosleep(&sleeptimer, NULL); } } } } } static void send_sip_tcp(t_event *event) { t_event_network *e; bool new_connection = false; e = (t_event_network *)event; unsigned long dst_addr = e->dst_addr; unsigned short dst_port = e->dst_port; assert(dst_addr != 0); assert(dst_port != 0); // Set correct transport in topmost Via header of a request. // For a response the Via header is copied from the incoming request. t_sip_message *sip_msg = e->get_msg(); if (sip_msg->get_type() == MSG_REQUEST) { sip_msg->hdr_via.via_list.front().transport = "TCP"; } t_connection *conn = NULL; // If a connection exists then re-use this connection. Otherwise a new connection // must be opened. // For a request a connection to the destination address and port of the event // must be opened. // For a response a connection to the sent-by address and port in the Via header // must be opened. if (sip_msg->get_encoded_size() <= MAX_REUSE_CONN_SIZE) { // Re-use a connection only for small messages. Large messages cause // head of line blocking. conn = connection_table->get_connection(dst_addr, dst_port); } else { log_file->write_report( "Open new connection for large message.", "::send_sip_tcp", LOG_SIP, LOG_DEBUG); } if (!conn) { if (sip_msg->get_type() == MSG_RESPONSE) { t_ip_port dst_ip_port; sip_msg->hdr_via.get_response_dst(dst_ip_port); dst_addr = dst_ip_port.ipaddr; dst_port = dst_ip_port.port; } t_socket_tcp *tcp = new t_socket_tcp(); MEMMAN_NEW(tcp); log_file->write_header("::send_sip_tcp", LOG_SIP, LOG_DEBUG); log_file->write_raw("Open connection to "); log_file->write_raw(h_ip2str(dst_addr)); log_file->write_raw(":"); log_file->write_raw(dst_port); log_file->write_endl(); log_file->write_footer(); try { tcp->connect(dst_addr, dst_port); } catch (int err) { evq_trans_mgr->push_failure(FAIL_TRANSPORT, sip_msg->hdr_via.via_list.front().branch, sip_msg->hdr_cseq.method); log_file->write_header("::send_sip_tcp", LOG_SIP, LOG_WARNING); log_file->write_raw("Failed to open connection to "); log_file->write_raw(h_ip2str(dst_addr)); log_file->write_raw(":"); log_file->write_raw(dst_port); log_file->write_endl(); log_file->write_footer(); delete tcp; MEMMAN_DELETE(tcp); return; } conn = new t_connection(tcp); MEMMAN_NEW(conn); // For large messages always a new connection is established. // No other messages should be sent via this connection to avoid // head of line blocking. if (sip_msg->get_encoded_size() > MAX_REUSE_CONN_SIZE) { conn->set_reuse(false); } new_connection = true; } // NOTE: if an existing connection was found, the connection table is now locked. // If persistent TCP connections are required, then // 1) If the SIP message is a registration request, then add the URI from the To-header // to the registered URI set of the connection. // 2) If the SIP message is a de-registration request then remove the URI from the // To-header from the registered URI set of the connection. if (sip_msg->get_type() == MSG_REQUEST) { t_request *req = dynamic_cast(sip_msg); if (req->method == REGISTER) { t_phone_user *pu = phone->find_phone_user(req->hdr_to.uri); if (pu) { t_user *user_config = pu->get_user_profile(); assert(user_config); if (user_config->get_persistent_tcp()) { conn->update_registered_uri_set(req); } } else { log_file->write_header("::send_sip_tcp", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Cannot find phone user for "); log_file->write_raw(req->hdr_to.uri.encode()); log_file->write_endl(); log_file->write_footer(); } } } string m = sip_msg->encode(); log_file->write_header("::send_sip_tcp", LOG_SIP); log_file->write_raw("Send to: tcp:"); log_file->write_raw(h_ip2str(dst_addr)); log_file->write_raw(":"); log_file->write_raw(dst_port); log_file->write_endl(); log_file->write_raw(m); log_file->write_endl(); log_file->write_footer(); conn->async_send(m.c_str(), m.size()); if (new_connection) { connection_table->add_connection(conn); } else { connection_table->unlock(); } } static void send_stun(t_event *event) { t_event_stun_request *e; e = (t_event_stun_request *)event; assert(e->dst_addr != 0); assert(e->dst_port != 0); log_file->write_header("::send_stun", LOG_STUN); log_file->write_raw("Send to: "); log_file->write_raw(h_ip2str(e->dst_addr)); log_file->write_raw(":"); log_file->write_raw(e->dst_port); log_file->write_endl(); log_file->write_raw(stunMsg2Str(*e->get_msg())); log_file->write_footer(); StunAtrString stun_pass; stun_pass.sizeValue = 0; char m[STUN_MAX_MESSAGE_SIZE]; int msg_size = stunEncodeMessage(*e->get_msg(), m, STUN_MAX_MESSAGE_SIZE, stun_pass, false); bool msg_sent = false; int transmit_count = 0; while (!msg_sent && transmit_count++ <= MAX_TRANSMIT_RETRIES) { try { sip_socket->sendto(e->dst_addr, e->dst_port, m, msg_size); num_non_icmp_errors = 0; msg_sent = true; } catch (int err) { if (!handle_socket_err(err, e->dst_addr, e->dst_port)) { // Discard packet. msg_sent = true; } else { if (transmit_count <= MAX_TRANSMIT_RETRIES) { // Sleep 100 ms struct timespec sleeptimer; sleeptimer.tv_sec = 0; sleeptimer.tv_nsec = 100000000; nanosleep(&sleeptimer, NULL); } } } } } static void send_nat_keepalive(t_event *event) { t_event_nat_keepalive *e = dynamic_cast(event); assert(e); assert(e->dst_addr != 0); assert(e->dst_port != 0); char m[2] = { '\r', '\n' }; bool msg_sent = false; int transmit_count = 0; while (!msg_sent && transmit_count++ <= MAX_TRANSMIT_RETRIES) { try { sip_socket->sendto(e->dst_addr, e->dst_port, m, 2); num_non_icmp_errors = 0; msg_sent = true; } catch (int err) { if (!handle_socket_err(err, e->dst_addr, e->dst_port)) { // Discard packet. msg_sent = true; } else { if (transmit_count <= MAX_TRANSMIT_RETRIES) { // Sleep 100 ms struct timespec sleeptimer; sleeptimer.tv_sec = 0; sleeptimer.tv_nsec = 100000000; nanosleep(&sleeptimer, NULL); } } } } } static void send_tcp_ping(t_event *event) { string log_msg; t_event_tcp_ping *e = dynamic_cast(event); assert(e); t_connection *conn = connection_table->get_connection( e->get_dst_addr(), e->get_dst_port()); if (!conn) { // There is no connection to send the ping. log_msg = "Connection to "; log_msg += h_ip2str(e->get_dst_addr()); log_msg += ":"; log_msg += int2str(e->get_dst_port()); log_msg += " is gone."; log_file->write_report(log_msg, "::send_tcp_ping", LOG_SIP, LOG_WARNING); // Signal the transaction layer that the connection is gone. evq_trans_layer->push_broken_connection(e->get_user_uri()); return; } conn->async_send(TCP_PING_PACKET, strlen(TCP_PING_PACKET)); connection_table->unlock(); } void *tcp_sender_loop(void *arg) { string log_msg; list writable_connections; while(true) { writable_connections.clear(); writable_connections = connection_table->select_write(NULL); if (writable_connections.empty()) { // Another thread cancelled the select command. // Stop listening. break; } // NOTE: The connection table is now locked. for (list::iterator it = writable_connections.begin(); it != writable_connections.end(); ++it) { try { (*it)->write(); } catch (int err) { if (err == EAGAIN || err == EWOULDBLOCK || err == EINTR) { continue; } unsigned long remote_addr; unsigned short remote_port; (*it)->get_remote_address(remote_addr, remote_port); log_msg = "Got error on socket to "; log_msg += h_ip2str(remote_addr); log_msg += ":"; log_msg += int2str(remote_port); log_msg += " - "; log_msg += get_error_str(err); log_file->write_report(log_msg, "::tcp_sender_loop", LOG_SIP, LOG_WARNING); // Connection is broken. // Signal the transaction layer that the connection is broken for // all associated registered URI's. const list &uris = (*it)->get_registered_uri_set(); for (list::const_iterator it_uri = uris.begin(); it_uri != uris.end(); ++it_uri) { evq_trans_layer->push_broken_connection(*it_uri); } // Remove the broken connection. connection_table->remove_connection(*it); MEMMAN_DELETE(*it); delete *it; continue; } } connection_table->unlock(); } log_file->write_report("TCP sender terminated.", "::tcp_sender_loop"); return NULL; } void *sender_loop(void *arg) { t_event *event; t_event_network *ev_network; unsigned long local_ipaddr; bool quit = false; while (!quit) { event = evq_sender->pop(); switch(event->get_type()) { case EV_NETWORK: ev_network = dynamic_cast(event); local_ipaddr = get_src_ip4_address_for_dst(ev_network->dst_addr); if (local_ipaddr == 0) { log_file->write_header("::sender_loop", LOG_NORMAL, LOG_CRITICAL); log_file->write_raw("Cannot get source IP address for destination: "); log_file->write_raw(h_ip2str(ev_network->dst_addr)); log_file->write_endl(); log_file->write_footer(); evq_trans_mgr->push_failure(FAIL_TRANSPORT, ev_network->get_msg()->hdr_via.via_list.front().branch, ev_network->get_msg()->hdr_cseq.method); break; } if (!ev_network->get_msg()->local_ip_check()) { log_file->write_report("Local IP check failed", "::sender_loop", LOG_NORMAL, LOG_CRITICAL); break; } if (ev_network->transport == "udp") { send_sip_udp(event); } else if (ev_network->transport == "tcp") { send_sip_tcp(event); } else { log_file->write_header("::sender_loop", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Received unsupported transport: "); log_file->write_raw(ev_network->transport); log_file->write_endl(); log_file->write_footer(); } break; case EV_STUN_REQUEST: send_stun(event); break; case EV_NAT_KEEPALIVE: send_nat_keepalive(event); break; case EV_TCP_PING: send_tcp_ping(event); break; case EV_QUIT: quit = true; break; default: assert(false); } MEMMAN_DELETE(event); delete event; } return NULL; } twinkle-1.10.1/src/sender.h000066400000000000000000000017131277565361200155070ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Network sender threads. */ #ifndef _H_SENDER #define _H_SENDER /** Thread sending SIP messages */ void *sender_loop(void *arg); /** Thread for sending asynchronously over TCP */ void *tcp_sender_loop(void *arg); #endif twinkle-1.10.1/src/sequence_number.h000066400000000000000000000073701277565361200174140ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Sequence number operations. */ #ifndef _SEQUENCE_NUMBER_H #define _SEQUENCE_NUMBER_H /** * Sequence numbers. * Sequence numbers with comparison operators that deal with sequence number * roll-overs using serial number arithmetic. * See http://en.wikipedia.org/wiki/Serial_Number_Arithmetic * * @param U The unsigned int type for the sequence number. * @param S The corresponsing signed int type having the same width. */ template< typename U, typename S > class sequence_number_t { private: /** * The sequence number. */ U _number; public: /** * Constructor. * @param number The sequence number. */ explicit sequence_number_t(U number) : _number(number) {}; /** * Get the sequence number. * @return The sequence number. */ U get_number(void) const { return _number; } /** * Cast to the sequence number. */ operator U(void) const { return get_number(); } /** * Calculate the distance to another sequence number. * @param number The sequence number to which the distance must be calculated. * @return The distance. */ S distance(const sequence_number_t &number) const { return static_cast(_number - number.get_number()); } /** * Calculate the distance to another distance sequence number. * @param number The sequence number to which the distance must be calculated. * @return The distance. */ S operator-(const sequence_number_t &number) const { return distance(number); } /** * Less-than comparison. * @param number The sequence number to compare with. * @return true, if this sequence number is less than number. * @return false, otherwise. */ bool operator<(const sequence_number_t &number) const { return (distance(number) < 0); } /** * Less-than-equal comparison. * @param number The sequence number to compare with. * @return true, if this sequence number is less than or equal to number. * @return false, otherwise. */ bool operator<=(const sequence_number_t &number) const { return (distance(number) <= 0); } /** * Equality comparison. * @param number The sequence number to compare with. * @return true, if this sequence number is equal to number. * @return false, otherwise. */ bool operator==(const sequence_number_t &number) const { return (number.get_number() == _number); } /** * Greater-than comparison. * @param number The sequence number to compare with. * @return true, if this sequence number is greater than number. * @return false, otherwise. */ bool operator>(const sequence_number_t &number) const { return (distance(number) > 0); } /** * Greater-than-equal comparison. * @param number The sequence number to compare with. * @return true, if this sequence number is greater than or equal to number. * @return false, otherwise. */ bool operator>=(const sequence_number_t &number) const { return (distance(number) >= 0); } }; /** * 16-bit sequence number */ typedef sequence_number_t seq16_t; /** * 32-bit sequence number */ typedef sequence_number_t seq32_t; #endif twinkle-1.10.1/src/service.cpp000066400000000000000000000210731277565361200162230ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include #include "service.h" #include "log.h" #include "userintf.h" #include "util.h" #define FLD_CF_ALWAYS "cf_always" #define FLD_CF_BUSY "cf_busy" #define FLD_CF_NOANSWER "cf_noanswer" #define FLD_DND "dnd" #define FLD_AUTO_ANSWER "auto_answer" void t_service::lock() { mtx_service.lock(); } void t_service::unlock() { mtx_service.unlock(); } t_service::t_service(t_user *user) { user_config = user; // Call redirection cf_always_active = false; cf_busy_active = false; cf_noanswer_active = false; // Do not disturb dnd_active = false; // Auto answer auto_answer_active = false; string msg; (void)read_config(msg); } bool t_service::multiple_services_active(void) { int num_services = 0; if (is_cf_active()) num_services++; if (is_dnd_active()) num_services++; if (is_auto_answer_active()) num_services++; if (num_services > 1) return true; return false; } void t_service::enable_cf(t_cf_type cf_type, const list &cf_dest) { lock(); switch (cf_type) { case CF_ALWAYS: cf_always_active = true; cf_always_dest = cf_dest; break; case CF_BUSY: cf_busy_active = true; cf_busy_dest = cf_dest; break; case CF_NOANSWER: cf_noanswer_active = true; cf_noanswer_dest = cf_dest; break; default: assert(false); } unlock(); string msg; (void)write_config(msg); } void t_service::disable_cf(t_cf_type cf_type) { lock(); switch (cf_type) { case CF_ALWAYS: cf_always_active = false; cf_always_dest.clear(); break; case CF_BUSY: cf_busy_active = false; cf_busy_dest.clear(); break; case CF_NOANSWER: cf_noanswer_active = false; cf_noanswer_dest.clear(); break; default: assert(false); } unlock(); string msg; (void)write_config(msg); } bool t_service::get_cf_active(t_cf_type cf_type, list &dest) { bool active = false; lock(); switch (cf_type) { case CF_ALWAYS: active = cf_always_active; dest = cf_always_dest; break; case CF_BUSY: active = cf_busy_active; dest = cf_busy_dest; break; case CF_NOANSWER: active = cf_noanswer_active; dest = cf_noanswer_dest; break; default: assert(false); } unlock(); return active; } bool t_service::is_cf_active(void) { bool active = false; lock(); active = cf_always_active || cf_busy_active || cf_noanswer_active; unlock(); return active; } list t_service::get_cf_dest(t_cf_type cf_type) { list dest; lock(); switch (cf_type) { case CF_ALWAYS: dest = cf_always_dest; break; case CF_BUSY: dest = cf_busy_dest; break; case CF_NOANSWER: dest = cf_noanswer_dest; break; default: assert(false); } unlock(); return dest; } void t_service::enable_dnd(void) { lock(); dnd_active = true; unlock(); string msg; (void)write_config(msg); } void t_service::disable_dnd(void) { lock(); dnd_active = false; unlock(); string msg; (void)write_config(msg); } bool t_service::is_dnd_active(void) const { return dnd_active; } void t_service::enable_auto_answer(bool on) { lock(); auto_answer_active = on; unlock(); string msg; (void)write_config(msg); } bool t_service::is_auto_answer_active(void) const { return auto_answer_active; } bool t_service::read_config(string &error_msg) { struct stat stat_buf; lock(); string filename = user_config->get_profile_name() + SVC_FILE_EXT; string f = user_config->expand_filename(filename); // Check if config file exists if (stat(f.c_str(), &stat_buf) != 0) { unlock(); return true; } // Open file ifstream config(f.c_str()); if (!config) { error_msg = "Cannot open file for reading: "; error_msg += f; log_file->write_report(error_msg, "t_service::read_config", LOG_NORMAL, LOG_CRITICAL); unlock(); return false; } t_display_url display_url; cf_always_active = false; cf_always_dest.clear(); cf_busy_active = false; cf_busy_dest.clear(); cf_noanswer_active = false; cf_noanswer_dest.clear(); while (!config.eof()) { string line; getline(config, line); // Check if read operation succeeded if (!config.good() && !config.eof()) { error_msg = "File system error while reading file "; error_msg += f; log_file->write_report(error_msg, "t_service::read_config", LOG_NORMAL, LOG_CRITICAL); unlock(); return false; } line = trim(line); // Skip empty lines if (line.size() == 0) continue; // Skip comment lines if (line[0] == '#') continue; vector l = split_on_first(line, '='); if (l.size() != 2) { error_msg = "Syntax error in file "; error_msg += f; error_msg += "\n"; error_msg += line; log_file->write_report(error_msg, "t_service::read_config", LOG_NORMAL, LOG_CRITICAL); unlock(); return false; } string parameter = trim(l[0]); string value = trim(l[1]); if (parameter == FLD_CF_ALWAYS) { ui->expand_destination(user_config, value, display_url); if (display_url.is_valid()) { cf_always_active = true; cf_always_dest.push_back(display_url); } } else if (parameter == FLD_CF_BUSY) { ui->expand_destination(user_config, value, display_url); if (display_url.is_valid()) { cf_busy_active = true; cf_busy_dest.push_back(display_url); } } else if (parameter == FLD_CF_NOANSWER) { ui->expand_destination(user_config, value, display_url); if (display_url.is_valid()) { cf_noanswer_active = true; cf_noanswer_dest.push_back(display_url); } } else if (parameter == FLD_DND) { dnd_active = yesno2bool(value); } else if (parameter == FLD_AUTO_ANSWER) { auto_answer_active = yesno2bool(value); } else { // Ignore unknown parameters. Only report in log file. log_file->write_header("t_service::read_config", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Unknown parameter in service config: "); log_file->write_raw(parameter); log_file->write_endl(); log_file->write_footer(); } } unlock(); return true; } bool t_service::write_config(string &error_msg) { struct stat stat_buf; lock(); string filename = user_config->get_profile_name() + SVC_FILE_EXT; string f = user_config->expand_filename(filename); // Make a backup of the file if we are editing an existing file, so // that can be restored when writing fails. string f_backup = f + '~'; if (stat(f.c_str(), &stat_buf) == 0) { if (rename(f.c_str(), f_backup.c_str()) != 0) { string err = get_error_str(errno); error_msg = "Failed to backup "; error_msg += f; error_msg += " to "; error_msg += f_backup; error_msg += "\n"; error_msg += err; log_file->write_report(error_msg, "t_service::write_config", LOG_NORMAL, LOG_CRITICAL); unlock(); return false; } } ofstream config(f.c_str()); if (!config) { error_msg = "Cannot open file for writing: "; error_msg += f; log_file->write_report(error_msg, "t_user::write_config", LOG_NORMAL, LOG_CRITICAL); unlock(); return false; } for (list::iterator i = cf_always_dest.begin(); i != cf_always_dest.end(); i++) { config << FLD_CF_ALWAYS << '=' << i->encode() << endl; } for (list::iterator i = cf_busy_dest.begin(); i != cf_busy_dest.end(); i++) { config << FLD_CF_BUSY << '=' << i->encode() << endl; } for (list::iterator i = cf_noanswer_dest.begin(); i != cf_noanswer_dest.end(); i++) { config << FLD_CF_NOANSWER << '=' << i->encode() << endl; } config << FLD_DND << '=' << bool2yesno(dnd_active) << endl; config << FLD_AUTO_ANSWER << '=' << bool2yesno(auto_answer_active) << endl; // Check if writing succeeded if (!config.good()) { // Restore backup config.close(); rename(f_backup.c_str(), f.c_str()); error_msg = "File system error while writing file "; error_msg += f; log_file->write_report(error_msg, "t_service::write_config", LOG_NORMAL, LOG_CRITICAL); unlock(); return false; } unlock(); return true; } twinkle-1.10.1/src/service.h000066400000000000000000000046721277565361200156760ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _H_SERVICE #define _H_SERVICE #include #include "user.h" #include "sockets/url.h" #include "threads/mutex.h" #define SVC_FILE_EXT ".svc" using namespace std; // Call forwarding types enum t_cf_type { CF_ALWAYS, CF_BUSY, CF_NOANSWER }; class t_service { private: // Protect operations on the service t_mutex mtx_service; t_user *user_config; // Call redirection (call forwarding) bool cf_always_active; list cf_always_dest; bool cf_busy_active; list cf_busy_dest; bool cf_noanswer_active; list cf_noanswer_dest; // Do not disturb // Note: CF_ALWAYS takes precedence over DND bool dnd_active; // Auto answer bool auto_answer_active; void lock(); void unlock(); t_service() {}; public: t_service(t_user *user); // All methods first lock the mtx_service mutex before executing // and unlock on return to guarantee the service data does not // get changed by other threads during execution. // General // Is more than 1 service active? // Different types of call forwarding counts as 1. bool multiple_services_active(void); // Call forwarding void enable_cf(t_cf_type cf_type, const list &cf_dest); void disable_cf(t_cf_type cf_type); bool get_cf_active(t_cf_type cf_type, list &dest); bool is_cf_active(void); // is any cf active? list get_cf_dest(t_cf_type cf_type); // Do not disturb void enable_dnd(void); void disable_dnd(void); bool is_dnd_active(void) const; // Auto answer void enable_auto_answer(bool on); bool is_auto_answer_active(void) const; // Read/write service settings to file bool read_config(string &error_msg); bool write_config(string &error_msg); }; #endif twinkle-1.10.1/src/session.cpp000066400000000000000000000576311277565361200162570ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include "line.h" #include "log.h" #include "phone.h" #include "phone_user.h" #include "session.h" #include "util.h" #include "userintf.h" #include "audits/memman.h" extern string user_host; extern string local_hostname; extern t_phone *phone; /////////// // PRIVATE /////////// void t_session::set_recvd_codecs(t_sdp *sdp) { recvd_codecs.clear(); send_ac2payload.clear(); send_payload2ac.clear(); list payloads = sdp->get_codecs(SDP_AUDIO); for (list::iterator i = payloads.begin(); i != payloads.end(); i++) { t_audio_codec ac = sdp->get_codec(SDP_AUDIO, *i); if (ac > CODEC_UNSUPPORTED) { recvd_codecs.push_back(ac); send_ac2payload[ac] = *i; send_payload2ac[*i] = ac; } } } bool t_session::is_3way(void) const { t_line *l = get_line(); t_phone *p = l->get_phone(); return p->part_of_3way(l->get_line_number()); } t_session *t_session::get_peer_3way(void) const { t_line *l = get_line(); t_phone *p = l->get_phone(); t_line *peer_line = p->get_3way_peer_line(l->get_line_number()); return peer_line->get_session(); } /////////// // PUBLIC /////////// t_session::t_session(t_dialog *_dialog, string _receive_host, unsigned short _receive_port) { dialog = _dialog; user_config = dialog->get_line()->get_user(); assert(user_config); receive_host = _receive_host; retrieve_host = _receive_host; receive_port = _receive_port; src_sdp_version = int2str(rand()); src_sdp_id = int2str(rand()); use_codec = CODEC_NULL; switch (user_config->get_dtmf_transport()) { case DTMF_RFC2833: case DTMF_AUTO: recv_dtmf_pt = user_config->get_dtmf_payload_type(); break; default: recv_dtmf_pt = 0; } send_dtmf_pt = 0; offer_codecs = user_config->get_codecs(); ptime = user_config->get_ptime(); ilbc_mode = user_config->get_ilbc_mode(); recvd_offer = false; recvd_answer = false; sent_offer = false; direction = SDP_SENDRECV; audio_rtp_session = NULL; is_on_hold = false; is_killed = false; // Initialize audio codec to payload mappings recv_ac2payload[CODEC_G711_ULAW] = SDP_FORMAT_G711_ULAW; recv_ac2payload[CODEC_G711_ALAW] = SDP_FORMAT_G711_ALAW; recv_ac2payload[CODEC_GSM] = SDP_FORMAT_GSM; recv_ac2payload[CODEC_G729A] = SDP_FORMAT_G729; recv_ac2payload[CODEC_SPEEX_NB] = user_config->get_speex_nb_payload_type(); recv_ac2payload[CODEC_SPEEX_WB] = user_config->get_speex_wb_payload_type(); recv_ac2payload[CODEC_SPEEX_UWB] = user_config->get_speex_uwb_payload_type(); recv_ac2payload[CODEC_ILBC] = user_config->get_ilbc_payload_type(); recv_ac2payload[CODEC_G726_16] = user_config->get_g726_16_payload_type(); recv_ac2payload[CODEC_G726_24] = user_config->get_g726_24_payload_type(); recv_ac2payload[CODEC_G726_32] = user_config->get_g726_32_payload_type(); recv_ac2payload[CODEC_G726_40] = user_config->get_g726_40_payload_type(); recv_ac2payload[CODEC_TELEPHONE_EVENT] = user_config->get_dtmf_payload_type(); send_ac2payload.clear(); // Initialize pauload to audio codec mappings recv_payload2ac[SDP_FORMAT_G711_ULAW] = CODEC_G711_ULAW; recv_payload2ac[SDP_FORMAT_G711_ALAW] = CODEC_G711_ALAW; recv_payload2ac[SDP_FORMAT_GSM] = CODEC_GSM; recv_payload2ac[SDP_FORMAT_G729] = CODEC_G729A; recv_payload2ac[user_config->get_speex_nb_payload_type()] = CODEC_SPEEX_NB; recv_payload2ac[user_config->get_speex_wb_payload_type()] = CODEC_SPEEX_WB; recv_payload2ac[user_config->get_speex_uwb_payload_type()] = CODEC_SPEEX_UWB; recv_payload2ac[user_config->get_ilbc_payload_type()] = CODEC_ILBC; recv_payload2ac[user_config->get_g726_16_payload_type()] = CODEC_G726_16; recv_payload2ac[user_config->get_g726_24_payload_type()] = CODEC_G726_24; recv_payload2ac[user_config->get_g726_32_payload_type()] = CODEC_G726_32; recv_payload2ac[user_config->get_g726_40_payload_type()] = CODEC_G726_40; recv_payload2ac[user_config->get_dtmf_payload_type()] = CODEC_TELEPHONE_EVENT; send_payload2ac.clear(); } t_session::~t_session() { stop_rtp(); } t_session *t_session::create_new_version(void) const { t_session *s = new t_session(*this); MEMMAN_NEW(s); s->src_sdp_version = int2str(atoi(src_sdp_version.c_str()) + 1); s->recvd_codecs.clear(); s->recvd_offer = false; s->recvd_answer = false; s->sent_offer = false; // Do not copy the RTP session s->set_audio_session(NULL); // Clear the codec to payload mappings as a new response must // be received from the far end s->send_ac2payload.clear(); s->send_payload2ac.clear(); return s; } t_session *t_session::create_call_hold(void) const { t_session *s = create_new_version(); if (user_config->get_hold_variant() == HOLD_RFC2543) { s->receive_host = "0.0.0.0"; } else if (user_config->get_hold_variant() == HOLD_RFC3264) { // RFC 3264 8.4 if (direction == SDP_SENDRECV) { s->direction = SDP_SENDONLY; } else if (direction == SDP_RECVONLY) { s->direction = SDP_INACTIVE; } } else { assert(false); } // Prevent RTP from being started for this session as long // as the call is put on hold. Without this, the RTP sessions // will get started when a re-INVITE is received from the far-end // while the call is still locally on-hold. s->hold(); return s; } t_session *t_session::create_call_retrieve(void) const { t_session *s = create_new_version(); if (user_config->get_hold_variant() == HOLD_RFC2543) { s->receive_host = retrieve_host; } else if (user_config->get_hold_variant() == HOLD_RFC3264) { // RFC 3264 8.4 if (direction == SDP_SENDONLY) { s->direction = SDP_SENDRECV; } else if (direction == SDP_INACTIVE) { s->direction = SDP_RECVONLY; } } else { assert(false); } return s; } t_session *t_session::create_clean_copy(void) const { t_session *s = new t_session(*this); MEMMAN_NEW(s); s->src_sdp_version = int2str(atoi(src_sdp_version.c_str()) + 1); s->dst_sdp_version = ""; s->dst_sdp_id = ""; s->dst_rtp_host = ""; s->dst_rtp_port = 0; s->recvd_codecs.clear(); s->recvd_offer = false; s->recvd_answer = false; s->sent_offer = false; s->direction = SDP_SENDRECV; // Do not copy the RTP session s->set_audio_session(NULL); // Clear the codec to payload mappings as a new response must // be received from the far end s->send_ac2payload.clear(); s->send_payload2ac.clear(); return s; } bool t_session::process_sdp_offer(t_sdp *sdp, int &warn_code, string &warn_text) { if (!sdp->is_supported(warn_code, warn_text)) return false; dst_sdp_version = sdp->origin.session_version; dst_sdp_id = sdp->origin.session_id; recvd_sdp_offer = *sdp; // RFC 3264 5 // SDP may contain 0 m= lines if (sdp->media.empty()) return true; dst_rtp_host = sdp->get_rtp_host(SDP_AUDIO); dst_rtp_port = sdp->get_rtp_port(SDP_AUDIO); set_recvd_codecs(sdp); dst_zrtp_support = sdp->get_zrtp_support(SDP_AUDIO); // The direction in the SDP is from the point of view of the // far end. Swap the direction to store it as the point of view // from the near end. switch(sdp->get_direction(SDP_AUDIO)) { case SDP_INACTIVE: direction = SDP_INACTIVE; break; case SDP_SENDONLY: if (is_on_hold && user_config->get_hold_variant() == HOLD_RFC3264) { // The phone is put on-hold. We don't want to // receive media. direction = SDP_INACTIVE; } else { direction = SDP_RECVONLY; } break; case SDP_RECVONLY: direction = SDP_SENDONLY; break; case SDP_SENDRECV: if (is_on_hold && user_config->get_hold_variant() == HOLD_RFC3264) { // The phone is put on-hold. We don't want to // receive media. direction = SDP_SENDONLY; } else { direction = SDP_SENDRECV; } break; default: assert(false); } // Check if the list of received codecs has at least 1 codec // in common with the list of codecs we can offer. If there // is no common codec, then no call can be established. list::iterator supported_codec_it = offer_codecs.end(); for (list::const_iterator i = recvd_codecs.begin(); i != recvd_codecs.end(); i++) { list::iterator tmp_it; if ((supported_codec_it == offer_codecs.end() || !user_config->get_in_obey_far_end_codec_pref()) && (tmp_it = std::find(offer_codecs.begin(), supported_codec_it, *i)) != supported_codec_it) { // Codec supported supported_codec_it = tmp_it; use_codec = *i; // this codec goes into answer // Use the payload to codec bindings as signalled in the // offer by the far end. recv_payload2ac[send_ac2payload[use_codec]] = use_codec; recv_ac2payload[use_codec] = send_ac2payload[use_codec]; } else if (*i == CODEC_TELEPHONE_EVENT) { // telephone-event payload is supported send_dtmf_pt = send_ac2payload[*i]; // When we support RFC 2833 events, then take the payload // type from the far end. if (recv_dtmf_pt > 0) { recv_dtmf_pt = send_dtmf_pt; // this goes into answer as well } } } if (supported_codec_it == offer_codecs.end()) { warn_code = W_305_INCOMPATIBLE_MEDIA_FORMAT; warn_text = "None of the audio codecs is supported"; return false; } // Overwrite ptime value with ptime from SDP unsigned short p = sdp->get_ptime(SDP_AUDIO); if (p > 0) ptime = p; // RFC 3952 5 // Select the iLBC mode that needs the lowest bandwidth if (use_codec == CODEC_ILBC) { int recvd_mode = sdp->get_fmtp_int_param(SDP_AUDIO, send_ac2payload[use_codec], "mode"); if (recvd_mode == -1) recvd_mode = 30; if (VALID_ILBC_MODE(recvd_mode) && recvd_mode > ilbc_mode) { ilbc_mode = static_cast(recvd_mode); } } return true; } bool t_session::process_sdp_answer(t_sdp *sdp, int &warn_code, string &warn_text) { if (!sdp->is_supported(warn_code, warn_text)) return false; // As our offer always contains an audio m= line, the answer // should contain one as well. If there are media lines, then // the sdp->is_supported already verified there is audio. if (sdp->media.empty()) { warn_code = W_304_MEDIA_TYPE_NOT_AVAILABLE; warn_text = "Valid media stream for audio is missing"; return false; } dst_sdp_version = sdp->origin.session_version; dst_sdp_id = sdp->origin.session_id; dst_rtp_host = sdp->get_rtp_host(SDP_AUDIO); dst_rtp_port = sdp->get_rtp_port(SDP_AUDIO); dst_zrtp_support = sdp->get_zrtp_support(SDP_AUDIO); set_recvd_codecs(sdp); // Find the first codec in the received codecs list that // is supported. // Per the offer/answer model all received codecs should be // supported! It seems that some applications put more codecs // in the answer though. list::iterator codec_found_it = offer_codecs.end(); for (list::const_iterator i = recvd_codecs.begin(); i != recvd_codecs.end(); i++) { list::iterator tmp_it; if ((codec_found_it == offer_codecs.end() || !user_config->get_out_obey_far_end_codec_pref()) && (tmp_it = std::find(offer_codecs.begin(), codec_found_it, *i)) != codec_found_it) { codec_found_it = tmp_it; use_codec = *i; } else if (*i == CODEC_TELEPHONE_EVENT) { // telephone-event payload is supported send_dtmf_pt = send_ac2payload[*i]; } } if (codec_found_it == offer_codecs.end()) { // None of the answered codecs is supported warn_code = W_305_INCOMPATIBLE_MEDIA_FORMAT; warn_text = "None of the codecs is supported"; return false; } // Overwrite ptime value with ptime from SDP unsigned short p = sdp->get_ptime(SDP_AUDIO); if (p > 0) ptime = p; // RFC 3952 5 // Select the iLBC mode that needs the lowest bandwidth if (use_codec == CODEC_ILBC) { int recvd_mode = sdp->get_fmtp_int_param(SDP_AUDIO, send_ac2payload[use_codec], "mode"); if (recvd_mode == -1) recvd_mode = 30; if (VALID_ILBC_MODE(recvd_mode) && recvd_mode > ilbc_mode) { ilbc_mode = static_cast(recvd_mode); } } return true; } void t_session::create_sdp_offer(t_sip_message *m, const string &user) { // Delete old body if present if (m->body) { MEMMAN_DELETE(m->body); delete m->body; } // Determine the IP address to receive the media streams if (receive_host == AUTO_IP4_ADDRESS) { unsigned local_ip = m->get_local_ip(); if (local_ip == 0) { log_file->write_report("Cannot determine local IP address.", "t_session::create_sdp_offer", LOG_NORMAL, LOG_CRITICAL); } else { receive_host = USER_HOST(user_config, h_ip2str(local_ip)); retrieve_host = receive_host; } } m->body = new t_sdp(user, src_sdp_id, src_sdp_version, receive_host, receive_host, receive_port, offer_codecs, recv_dtmf_pt, recv_ac2payload); MEMMAN_NEW(m->body); // Set ptime for G711/G726 codecs list::iterator it_g7xx; it_g7xx = find(offer_codecs.begin(), offer_codecs.end(), CODEC_G711_ALAW); if (it_g7xx == offer_codecs.end()) { it_g7xx = find(offer_codecs.begin(), offer_codecs.end(), CODEC_G711_ULAW); } if (it_g7xx == offer_codecs.end()) { it_g7xx = find(offer_codecs.begin(), offer_codecs.end(), CODEC_G726_16); } if (it_g7xx == offer_codecs.end()) { it_g7xx = find(offer_codecs.begin(), offer_codecs.end(), CODEC_G726_24); } if (it_g7xx == offer_codecs.end()) { it_g7xx = find(offer_codecs.begin(), offer_codecs.end(), CODEC_G726_32); } if (it_g7xx == offer_codecs.end()) { it_g7xx = find(offer_codecs.begin(), offer_codecs.end(), CODEC_G726_40); } if (it_g7xx != offer_codecs.end()) { ((t_sdp *)m->body)->set_ptime(SDP_AUDIO, ptime); } // Set mode for iLBC codecs list::iterator it_ilbc; it_ilbc = find(offer_codecs.begin(), offer_codecs.end(), CODEC_ILBC); if (it_ilbc != offer_codecs.end() && ilbc_mode != 30) { ((t_sdp *)m->body)->set_fmtp_int_param(SDP_AUDIO, recv_ac2payload[CODEC_ILBC], "mode", ilbc_mode); } // Set direction if (direction != SDP_SENDRECV) { ((t_sdp *)m->body)->set_direction(SDP_AUDIO, direction); } // Set zrtp support if (user_config->get_zrtp_enabled() && user_config->get_zrtp_sdp()) { ((t_sdp *)m->body)->set_zrtp_support(SDP_AUDIO); } m->hdr_content_type.set_media(t_media("application", "sdp")); sent_offer = true; } void t_session::create_sdp_answer(t_sip_message *m, const string &user) { // Delete old body if present if (m->body) { MEMMAN_DELETE(m->body); delete m->body; } // Determine the IP address to receive the media streams if (receive_host == AUTO_IP4_ADDRESS) { unsigned long local_ip = 0; unsigned long dst_ip = gethostbyname(dst_rtp_host); if (dst_ip != 0) { // Determine source IP address for RTP from the // destination RTP IP address. log_file->write_report("Cannot determine local IP address from RTP destination.", "t_session::create_sdp_answer", LOG_NORMAL, LOG_WARNING); local_ip = get_src_ip4_address_for_dst(dst_ip); } else { string log_msg = "Cannot determine IP address for: "; log_msg += dst_rtp_host; log_file->write_report(log_msg, "t_session::create_sdp_answer", LOG_NORMAL, LOG_WARNING); } if (local_ip == 0) { // Somehow the source IP address could not be determined // from the destination RTP address. Try to determine it // from the destination of the SIP message. local_ip = m->get_local_ip(); } if (local_ip == 0) { log_file->write_report("Cannot determine local IP address.", "t_session::create_sdp_answer", LOG_NORMAL, LOG_CRITICAL); } else { receive_host = USER_HOST(user_config, h_ip2str(local_ip)); retrieve_host = receive_host; } } list answer_codecs; answer_codecs.push_back(use_codec); // RFC 3264 6 // The answer must contain an m-line for each m-line in the offer in // the same order. Media can be rejected by setting the port to 0. // Only the first audio stream is accepted, all other media streams // will be rejected. m->body = new t_sdp(user, src_sdp_id, src_sdp_version, receive_host, receive_host); MEMMAN_NEW(m->body); bool audio_answered = false; for (list::const_iterator i = recvd_sdp_offer.media.begin(); i != recvd_sdp_offer.media.end(); i++) { if (!audio_answered && i->get_media_type() == SDP_AUDIO && i->port != 0) { // Accept the first audio stream ((t_sdp *)m->body)->add_media(t_sdp_media( SDP_AUDIO, receive_port, answer_codecs, recv_dtmf_pt, send_ac2payload)); audio_answered = true; } else { // Reject media stream by setting port to zero t_sdp_media reject_media(*i); reject_media.port = 0; ((t_sdp *)m->body)->add_media(reject_media); } } m->hdr_content_type.set_media(t_media("application", "sdp")); // If there were no media lines in the offer, we sent no media // lines in the answer if (recvd_sdp_offer.media.empty()) return; // Set audio attributes // Set ptime for G711 codecs if (use_codec == CODEC_G711_ALAW || use_codec == CODEC_G711_ULAW) { ((t_sdp *)m->body)->set_ptime(SDP_AUDIO, ptime); } // Set mode for iLBC codecs if (use_codec == CODEC_ILBC && ilbc_mode != 30) { unsigned short ilbc_payload = const_cast(this)-> recv_ac2payload[CODEC_ILBC]; ((t_sdp *)m->body)->set_fmtp_int_param(SDP_AUDIO, ilbc_payload, "mode", ilbc_mode); } // Set direction if (direction != SDP_SENDRECV) { ((t_sdp *)m->body)->set_direction(SDP_AUDIO, direction); } // Set zrtp support if (user_config->get_zrtp_enabled() && user_config->get_zrtp_sdp()) { ((t_sdp *)m->body)->set_zrtp_support(SDP_AUDIO); } } void t_session::start_rtp(void) { // If a session is killed, it may not be started again. if (is_killed) { log_file->write_report("Cannot start. The session is killed already.", "t_session::start_rtp", LOG_NORMAL, LOG_DEBUG); return; } // If a session is on-hold then do not start RTP. if (is_on_hold) { log_file->write_report("Cannot start. The session is on hold.", "t_session::start_rtp", LOG_NORMAL, LOG_DEBUG); return; } if (receive_host.empty()) { log_file->write_report("Cannot start. receive_host is empty.", "t_session::start_rtp", LOG_NORMAL, LOG_DEBUG); return; } if (dst_rtp_host.empty()) { log_file->write_report("Cannot start. dst_rtp_host is empty.", "t_session::start_rtp", LOG_NORMAL, LOG_DEBUG); return; } // Local and remote hold if (((receive_host == "0.0.0.0" || receive_port == 0) && (dst_rtp_host == "0.0.0.0" || dst_rtp_port == 0)) || direction == SDP_INACTIVE) { log_file->write_report("Cannot start. Local and remote on hold.", "t_session::start_rtp", LOG_NORMAL, LOG_DEBUG); return; } // Inform user about the codecs get_line()->ci_set_send_codec(use_codec); get_line()->ci_set_recv_codec(use_codec); ui->cb_send_codec_changed(get_line()->get_line_number(), use_codec); ui->cb_recv_codec_changed(get_line()->get_line_number(), use_codec); // Determine ptime unsigned short audio_ptime; if (use_codec == CODEC_ILBC) { audio_ptime = ilbc_mode; } else { audio_ptime = ptime; } // Determine if audio must be encrypted bool encrypt_audio = get_line()->get_try_to_encrypt(); if (user_config->get_zrtp_send_if_supported()) { encrypt_audio = encrypt_audio && dst_zrtp_support; } // Start the RTP streams if (dst_rtp_host == "0.0.0.0" || dst_rtp_port == 0 || direction == SDP_RECVONLY) { // Local hold -> do not send RTP log_file->write_report("Local hold. Do not send RTP.", "t_session::start_rtp", LOG_NORMAL, LOG_DEBUG); audio_rtp_session = new t_audio_session(this, "0.0.0.0", get_line()->get_rtp_port(), "", 0, use_codec, audio_ptime, recv_payload2ac, send_ac2payload, encrypt_audio); MEMMAN_NEW(audio_rtp_session); } else if (receive_host == "0.0.0.0" || receive_port == 0 || direction == SDP_SENDONLY) { // Remote hold // For music on-hold music should be played here. // Without music on-hold do not send out RTP /* audio_rtp_session = new t_audio_session(this, "", 0, dst_rtp_host, dst_rtp_port, codec, ptime); */ log_file->write_report("Do not start. Remote hold.", "t_session::start_rtp", LOG_NORMAL, LOG_DEBUG); return; } else { // Bi-directional audio audio_rtp_session = new t_audio_session(this, "0.0.0.0", get_line()->get_rtp_port(), dst_rtp_host, dst_rtp_port, use_codec, audio_ptime, recv_payload2ac, send_ac2payload, encrypt_audio); MEMMAN_NEW(audio_rtp_session); } // Check if the created audio session is valid. if (!audio_rtp_session->is_valid()) { log_file->write_report("Audio session is invalid.", "t_session::start_rtp", LOG_NORMAL, LOG_CRITICAL); MEMMAN_DELETE(audio_rtp_session); delete audio_rtp_session; audio_rtp_session = NULL; return; } // Set dynamic payload type for DTMF events if (recv_dtmf_pt > 0) { unsigned short alt_dtmf_pt; if (recv_payload2ac.find(send_dtmf_pt) == recv_payload2ac.end()) { // Allow the payload type as signalled by the far end // as an alternative to the payload as signalled by Twinkle. alt_dtmf_pt = send_dtmf_pt; } else { // The payload type as signalled by the far end for DTMF // is already in use by Twinkle for another codec, so it // cannot be used as an alternative. alt_dtmf_pt = recv_dtmf_pt; } audio_rtp_session->set_pt_in_dtmf(recv_dtmf_pt, alt_dtmf_pt); } if (send_dtmf_pt > 0) { audio_rtp_session->set_pt_out_dtmf(send_dtmf_pt); switch (user_config->get_dtmf_transport()) { case DTMF_AUTO: case DTMF_RFC2833: get_line()->ci_set_dtmf_supported(true, false); break; case DTMF_INBAND: get_line()->ci_set_dtmf_supported(true, true); break; case DTMF_INFO: get_line()->ci_set_dtmf_supported(true, false, true); break; default: assert(false); } ui->cb_dtmf_supported(get_line()->get_line_number()); } else { switch (user_config->get_dtmf_transport()) { case DTMF_AUTO: case DTMF_INBAND: get_line()->ci_set_dtmf_supported(true, true); ui->cb_dtmf_supported(get_line()->get_line_number()); break; case DTMF_RFC2833: get_line()->ci_set_dtmf_supported(false); ui->cb_dtmf_not_supported(get_line()->get_line_number()); break; case DTMF_INFO: get_line()->ci_set_dtmf_supported(true, false, true); ui->cb_dtmf_supported(get_line()->get_line_number()); break; default: assert(false); } } audio_rtp_session->run(); } void t_session::stop_rtp(void) { if (audio_rtp_session) { MEMMAN_DELETE(audio_rtp_session); delete audio_rtp_session; audio_rtp_session = NULL; get_line()->ci_set_dtmf_supported(false); ui->cb_line_state_changed(); } } void t_session::kill_rtp(void) { stop_rtp(); is_killed = true; } t_audio_session *t_session::get_audio_session(void) const { return audio_rtp_session; } void t_session::set_audio_session(t_audio_session *as) { audio_rtp_session = as; } bool t_session::equal_audio(const t_session &s) const { // According to RFC 3264 6, the SDP version in the o= line // must be updated when the SDP is changed. // We check for more changes to interoperate with SIP // devices that do not adhere fully to RFC 3264 return (receive_host == s.receive_host && receive_port == s.receive_port && dst_rtp_host == s.dst_rtp_host && dst_rtp_port == s.dst_rtp_port && direction == s.direction && src_sdp_version == s.src_sdp_version && dst_sdp_version == s.dst_sdp_version && src_sdp_id == s.src_sdp_id && dst_sdp_id == s.dst_sdp_id); } void t_session::send_dtmf(char digit, bool inband) { if (audio_rtp_session) audio_rtp_session->send_dtmf(digit, inband); } t_line *t_session::get_line(void) const { return dialog->get_line(); } void t_session::set_owner(t_dialog *d) { dialog = d; } void t_session::hold(void) { is_on_hold = true; } void t_session::unhold(void) { is_on_hold = false; } bool t_session::is_rtp_active(void) const { return (audio_rtp_session != NULL); } twinkle-1.10.1/src/session.h000066400000000000000000000146351277565361200157210ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // Session description of an established session. // A session is the media part of a dialog. #ifndef _SESSION_H #define _SESSION_H #include #include #include #include "dialog.h" #include "user.h" #include "sdp/sdp.h" #include "parser/sip_message.h" #include "audio/audio_codecs.h" #include "audio/audio_session.h" // Forward declarations class t_dialog; class t_line; using namespace std; class t_session { private: // The owning dialog t_dialog *dialog; // User profile of user for which the session is created. // This is a pointer to the user_config owned by a phone user. // So this pointer should never be deleted. t_user *user_config; // Copy of host needed for call-retrieve after call-hold string retrieve_host; // Audio RTP session t_audio_session *audio_rtp_session; // Indicates if session is put on-hold, i.e. no RTP should be sent // or received for this session. bool is_on_hold; // Indicates if a session is killed, i.e. RTP will never be // sent or received anymore. bool is_killed; // Mapping from audio codecs to RTP payload numbers for receiving // and sending directions. map recv_ac2payload; map send_ac2payload; // Mapping from RTP payload numbers to audio codecs for receiving // and sending directions. map recv_payload2ac; map send_payload2ac; // Set the list of received codecs from the SDP. // Create the send_ac2paylaod and send_payload2ac mappings. void set_recvd_codecs(t_sdp *sdp); // Returns if this session is part of a 3-way conference bool is_3way(void) const; // Returns the peer session of a 3-way conference t_session *get_peer_3way (void) const; public: // Audio session information // Near end information string src_sdp_version; string src_sdp_id; string receive_host; // RTP receive host address unsigned short receive_port; // RTP receive port // Far end information string dst_sdp_version; string dst_sdp_id; string dst_rtp_host; unsigned short dst_rtp_port; bool dst_zrtp_support; // Direction of the audio stream from this phone's point of view t_sdp_media_direction direction; list offer_codecs; // codecs to offer in outgoing INVITE list recvd_codecs; // codecs received from far-end t_audio_codec use_codec; // codec to be used unsigned short ptime; // payload size (ms) unsigned short ilbc_mode; // 20 or 30 ms bool recvd_offer; // offer received? bool recvd_answer; // answer received? bool sent_offer; // offer sent? unsigned short recv_dtmf_pt; // payload type for DTMF receiving unsigned short send_dtmf_pt; // payload type for DTMF sending t_sdp recvd_sdp_offer; t_session(t_dialog *_dialog, string _receive_host, unsigned short _receive_port); // The destructor will destroy the RTP session and stop the // RTP streams ~t_session(); /** @name Clone a new session from an existing session. */ //@{ /** @note copies of a session do not copy the audio RTP session! */ /** * Create a session based on an existing session, i.e. * same receive user and host. The source SDP version of the * new session will be increased by 1. * @return The new session. */ t_session *create_new_version(void) const; /** * Create a copy of the session. The destination paramters * and recvd/offer and answer are erased in the copy. * The source SDP version of the new session will be increased by 1. * @return The new session. */ t_session *create_clean_copy(void) const; /** * Create a session for call-hold. * @return The call-hold session. */ t_session *create_call_hold(void) const; /** * Create a session for call-retrieve. * @return The call-retrieve session. */ t_session *create_call_retrieve(void) const; //@} // Process incoming SDP offer. Return false if SDP is not // supported. If SDP is supported then use_codec will be // set to the first codec in the received offer that is // supported by this phone, i.e. this is the codec that should // be put in the answer. bool process_sdp_offer(t_sdp *sdp, int &warn_code, string &warn_text); // Process incoming SDP answer. Return false if SDP is not // supported. It is expected that the answer contains 1 codec // only. If more codecs are answered, then only the first supported // codec is considered. bool process_sdp_answer(t_sdp *sdp, int &warn_code, string &warn_text); // Create an SDP offer body for a SIP message void create_sdp_offer(t_sip_message *m, const string &user); // Create an SDP answer body for a SIP message void create_sdp_answer(t_sip_message *m, const string &user); // Start/stop the RTP streams // When a session is on-hold then start_rtp simply returns. void start_rtp(void); void stop_rtp(void); // Kill RTP streams. The difference with stopping an RTP stream // is that it cannot be started after being killed. void kill_rtp(void); t_audio_session *get_audio_session(void) const; void set_audio_session(t_audio_session *as); // Check if two session are equal wrt the audio parameters bool equal_audio(const t_session &s) const; // Send DTMF digit void send_dtmf(char digit, bool inband); // Get the line that belongs to this session t_line *get_line(void) const; // Transfer ownership of this session to a new dialog void set_owner(t_dialog *d); // Hold/un-hold a session // These methods only toggle the hold indicator. If you hold // a session, you must make sure that any running RTP is stopped. // If you unhold a session you have to call start_rtp to start the // RTP. void hold(void); void unhold(void); // Check if RTP session is acitve bool is_rtp_active(void) const; }; #endif twinkle-1.10.1/src/sockets/000077500000000000000000000000001277565361200155275ustar00rootroot00000000000000twinkle-1.10.1/src/sockets/CMakeLists.txt000066400000000000000000000003521277565361200202670ustar00rootroot00000000000000project(libtwinkle-sockets) set(LIBTWINKLE_SOCKETS-SRCS connection.cpp connection_table.cpp dnssrv.cpp interfaces.cpp socket.cpp url.cpp ) add_library(libtwinkle-sockets OBJECT ${LIBTWINKLE_SOCKETS-SRCS}) twinkle-1.10.1/src/sockets/connection.cpp000066400000000000000000000171021277565361200203730ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "connection.h" #include #include #include "connection_table.h" #include "log.h" #include "sys_settings.h" #include "util.h" #include "audits/memman.h" #include "parser/parse_ctrl.h" extern t_connection_table *connection_table; using namespace std; t_connection::t_connection(t_socket *socket) : socket_(socket), sip_msg_(NULL), pos_send_buf_(0), idle_time_(0), can_reuse_(true) {} t_connection::~t_connection() { MEMMAN_DELETE(socket_); delete socket_; } t_socket *t_connection::get_socket(void) { return socket_; } t_connection::size_type t_connection::data_size(void) const { return read_buf_.size(); } void t_connection::read(bool &connection_closed) { connection_closed = false; char buf[READ_BLOCK_SIZE]; ssize_t nread = socket_->recv(buf, READ_BLOCK_SIZE); if (nread > 0) { read_buf_.append(buf, buf + nread); } else { connection_closed = true; } idle_time_ = 0; } void t_connection::write(void) { if (send_buf_.empty()) return; ssize_t nwrite = send_buf_.size() - pos_send_buf_; if ((ssize_t)WRITE_BLOCK_SIZE < nwrite) nwrite = WRITE_BLOCK_SIZE; ssize_t nwritten = socket_->send(send_buf_.c_str() + pos_send_buf_, nwrite); pos_send_buf_ += nwritten; if (pos_send_buf_ >= send_buf_.size()) { // All data written send_buf_.clear(); pos_send_buf_ = 0; } } ssize_t t_connection::send(const char *data, int data_size) { ssize_t bytes_sent = socket_->send(data, data_size); idle_time_ = 0; return bytes_sent; } void t_connection::async_send(const char *data, int data_size) { send_buf_ += string(data, data_size); connection_table->restart_write_select(); } t_sip_message *t_connection::get_sip_msg(string &raw_headers, string &raw_body, bool &error, bool &msg_too_large) { string log_msg; raw_headers.clear(); raw_body.clear(); error = false; msg_too_large = false; if (!sip_msg_) { // RFC 3261 7.5 // Ignore CRLF preceding the start-line of a SIP message while (read_buf_.size() >= 2 && read_buf_.substr(0, 2) == string(CRLF)) { remove_data(2); } // A complete list of headers has not been read yet, try // to find the boundary between headers and body. string seperator = string(CRLF) + string(CRLF); string::size_type pos_body = read_buf_.find(seperator); if (pos_body == string::npos) { // Still no complete list of headers. if (read_buf_.size() > sys_config->get_sip_max_tcp_size()) { log_file->write_report("Message too large", "t_connection::get_sip_msg", LOG_SIP, LOG_WARNING); error = true; } return NULL; } pos_body += seperator.size(); // Parse SIP headers raw_sip_headers_ = read_buf_.substr(0, pos_body); list parse_errors; try { sip_msg_ = t_parser::parse(raw_sip_headers_, parse_errors); } catch (int) { // Discard malformed SIP messages. log_msg = "Invalid SIP message.\n"; log_msg += "Fatal parse error in headers.\n\n"; log_msg += to_printable(raw_sip_headers_); log_file->write_report(log_msg, "t_connection::get_sip_msg", LOG_SIP, LOG_DEBUG); error = true; return NULL; } // Log non-fatal parse errors. if (!parse_errors.empty()) { log_msg = "Parse errors:\n"; log_msg += "\n"; for (list::iterator i = parse_errors.begin(); i != parse_errors.end(); i++) { log_msg += *i; log_msg += "\n"; } log_msg += "\n"; log_file->write_report(log_msg, "t_connection::get_sip_msg", LOG_SIP, LOG_DEBUG); } get_remote_address(sip_msg_->src_ip_port.ipaddr, sip_msg_->src_ip_port.port); sip_msg_->src_ip_port.transport = "tcp"; // Remove the processed headers from the read buffer. remove_data(pos_body); } // RFC 3261 18.4 // The Content-Length header field MUST be used with stream oriented transports. if (!sip_msg_->hdr_content_length.is_populated()) { // The transaction layer will send an error response. log_file->write_report("Content-Length header is missing.", "t_connection::get_sip_msg", LOG_SIP, LOG_WARNING); } else { if (read_buf_.size() < sip_msg_->hdr_content_length.length) { // No full body read yet. if (read_buf_.size() + raw_sip_headers_.size() <= sys_config->get_sip_max_tcp_size()) { return NULL; } else { log_file->write_report("Message too large", "t_connection::get_sip_msg", LOG_SIP, LOG_WARNING); msg_too_large = true; } } else { if (sip_msg_->hdr_content_length.length > 0) { raw_body = read_buf_.substr(0, sip_msg_->hdr_content_length.length); remove_data(sip_msg_->hdr_content_length.length); } } } // Return data to caller. Clear internally cached data. t_sip_message *msg = sip_msg_; sip_msg_ = NULL; raw_headers = raw_sip_headers_; raw_sip_headers_.clear(); return msg; } string t_connection::get_data(size_t nbytes) const { size_t nread = min(nbytes, read_buf_.size()); return read_buf_.substr(0, nread); } void t_connection::remove_data(size_t nbytes) { if (nbytes == 0) return; if (nbytes >= read_buf_.size()) { read_buf_.clear(); } else { read_buf_.erase(0, nbytes); } } void t_connection::get_remote_address(unsigned long &remote_addr, unsigned short &remote_port) { remote_addr = 0; remote_port = 0; try { t_socket_tcp *tcp_socket = dynamic_cast(socket_); if (tcp_socket) { tcp_socket->get_remote_address(remote_addr, remote_port); } else { log_file->write_report("Socket is not connection oriented.", "t_connection::get_sip_msg", LOG_NORMAL, LOG_WARNING); } } catch (int err) { string errmsg = get_error_str(err); string log_msg = "Cannot get remote address: "; log_msg += errmsg; log_file->write_report(log_msg, "t_connection::get_sip_msg", LOG_NORMAL, LOG_WARNING); } } unsigned long t_connection::increment_idle_time(unsigned long interval) { idle_time_ += interval; return idle_time_; } unsigned long t_connection::get_idle_time(void) const { return idle_time_; } bool t_connection::has_data_to_send(void) const { return !send_buf_.empty(); } void t_connection::set_reuse(bool reuse) { can_reuse_ = reuse; } bool t_connection::may_reuse(void) const { return can_reuse_; } void t_connection::add_registered_uri(const t_url &uri) { // Add the URI if it is not in the set. if (find(registered_uri_set_.begin(), registered_uri_set_.end(), uri) == registered_uri_set_.end()) { registered_uri_set_.push_back(uri); } } void t_connection::remove_registered_uri(const t_url &uri) { registered_uri_set_.remove(uri); } void t_connection::update_registered_uri_set(const t_request *req) { assert(req->method == REGISTER); if (req->is_registration_request()) { add_registered_uri(req->hdr_to.uri); } else if (req->is_de_registration_request()) { remove_registered_uri(req->hdr_to.uri); } } const list &t_connection::get_registered_uri_set(void) const { return registered_uri_set_; } bool t_connection::has_registered_uri(void) const { return !registered_uri_set_.empty(); } twinkle-1.10.1/src/sockets/connection.h000066400000000000000000000146101277565361200200410ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Network connection */ #ifndef _H_CONNECTION #define _H_CONNECTION #include #include #include "socket.h" #include "parser/request.h" #include "parser/sip_message.h" using namespace std; /** Abstract class for a network connection. */ class t_connection { private: static const unsigned int READ_BLOCK_SIZE = 1448; static const unsigned int WRITE_BLOCK_SIZE = 1448; /** Buffer with data already read from the network. */ string read_buf_; /** Socket for connection. */ t_socket *socket_; /** SIP message parsed from available data (headers only) */ t_sip_message *sip_msg_; /** Raw SIP headers that have been parsed already */ string raw_sip_headers_; /** Data to be sent on the connection. */ string send_buf_; /** Position in send buffer for next send action. */ string::size_type pos_send_buf_; /** * Time (ms) that the connection is idle . * This time is reset to zero by read and send actions. */ unsigned long idle_time_; /** * Flag to indicate that a connection can be reused. * By default a connection is reusable. */ bool can_reuse_; /** * A set of user URI's (AoR) that are registered via this connection. * If persistent connections are used for NAT traversal, then these are * the URI's that are impacted when the connection breaks. * A URI is only added to this set, if a persistent connection is required * for this user. * @note The set is implemented as a list as t_url has not less-than operator. */ list registered_uri_set_; public: typedef string::size_type size_type; t_connection(t_socket *socket); /** * Destuctor. * @note The socket will be closed and destroyed. */ virtual ~t_connection(); /** * Get a pointer to the socket. * @return The socket. */ t_socket *get_socket(void); /** * Get the amount of data in the read buffer. * @return Number of bytes in read buffer. */ size_type data_size(void) const; /** * Read a block data from connection in to read buffer. * @param connection_closed [out] Indicates if the connection was closed. * @throw int errno as set by recv. */ void read(bool &connection_closed); /** * Send a block of data from the send buffer on a connection. * @throw int errno. */ void write(void); /** * Send data on a connection. * @param data [in] Data to send * @param data_size [in] Size of data in bytes * @return Number of bytes sent. * @throw int errno. */ ssize_t send(const char *data, int data_size); /** * Append data to the send buffer for asynchronous sending. * @param data [in] Data to send * @param data_size [in] Size of data in bytes */ void async_send(const char *data, int data_size); /** * Get a SIP message from the connection. * @param raw_headers [out] Raw headers of SIP message * @param raw_body [out] Raw body of SIP message * @param error [out] Indicates if an error occurred (invalid SIP message) * @param msg_too_large [out] Indicates that the message is cutoff because it was too large * @return The SIP message if a message was received. * @return NULL, if no full SIP message has been received yet or an error occurred. * @post If error == true, then NULL is returned * @post If msg_too_large == true, then a message is returned (partial though) */ t_sip_message *get_sip_msg(string &raw_headers, string &raw_body, bool &error, bool &msg_too_large); /** * Get read data from read buffer. * @param nbytes [in] Maximum number of bytes to get. * @return Data from the read buffer up to nbytes. * @note The data is still in the buffer after this operation. */ string get_data(size_t nbytes = 0) const; /** * Remove data from read buffer. * @param nbytes [in] Number of bytes to remove. */ void remove_data(size_t nbytes); /** * Get the remote address of a connection. * @param remote_addr [out] Source IPv4 address of the connection. * @param remote_port [out] Source port of the connection. */ void get_remote_address(unsigned long &remote_addr, unsigned short &remote_port); /** * Add an interval to the idle time. * @param interval [in] Interval in ms. * @return The new idle time. */ unsigned long increment_idle_time(unsigned long interval); /** * Get idle time. * @return Idle time in ms. */ unsigned long get_idle_time(void) const; /** * Check if there is data in the send buffer. * @return true if there is data, otherwise false. */ bool has_data_to_send(void) const; /** Set re-use characteristic. */ void set_reuse(bool reuse); /** * Check if this connection may be reused to send data. * @return true if the connection may be reused, otherwise false. */ bool may_reuse(void) const; /** * Add a URI to the set of registered URI's. * @param uri [in] The URI to add. */ void add_registered_uri(const t_url &uri); /** * Remove a URI from the set of registered URI's. * @param uri [in] The URI to remove. */ void remove_registered_uri(const t_url &uri); /** * Update the set of registered URI based on a REGISTER request. * If the REGISTER is a registration, then add the To-header URI. * If the REGISTER is a de-registration, then remove the To-header URI. * If the REGISTER is a query, then do nothing. * @param req [in] A REGISTER request. * @pre req must be a REGISTER request. */ void update_registered_uri_set(const t_request *req); /** * Get the set of registered URI's. * @return The set of registered URI's. */ const list &get_registered_uri_set(void) const; /** * Check if at least one registered URI is associated with this connection. * @return True if a URI is associated, false otherwise. */ bool has_registered_uri(void) const; }; #endif twinkle-1.10.1/src/sockets/connection_table.cpp000066400000000000000000000254041277565361200215460ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "connection_table.h" #include #include #include #include #include #include #include #include #include "log.h" #include "protocol.h" #include "util.h" #include "audits/memman.h" using namespace std; extern t_connection_table *connection_table; void t_connection_table::create_pipe(int p[2]) { if (pipe(p) == -1) { string err = get_error_str(errno); cerr << "FATAL: t_connection_table - Cannot create pipe.\n"; cerr << err << endl; exit(-1); } if (fcntl(p[0], F_SETFL, O_NONBLOCK) == -1) { string err = get_error_str(errno); cerr << "FATAL: t_connection_table - fcntl fails on read side of pipe.\n"; cerr << err << endl; exit(-1); } if (fcntl(p[1], F_SETFL, O_NONBLOCK) == -1) { string err = get_error_str(errno); cerr << "FATAL: t_connection_table - fcntl fails on write side of pipe.\n"; cerr << err << endl; exit(-1); } } void t_connection_table::signal_modification_read(void) { t_mutex_guard guard(mtx_connections_); // Write a byte to the modified pipe, so a select can be retried. char x = 'x'; (void)write(fd_pipe_modified_read_[1], &x, 1); } void t_connection_table::signal_modification_write(void) { t_mutex_guard guard(mtx_connections_); // Write a byte to the modified pipe, so a select can be retried. char x = 'x'; (void)write(fd_pipe_modified_write_[1], &x, 1); } void t_connection_table::signal_quit(void) { t_mutex_guard guard(mtx_connections_); // Write a byte to the quit pipe, so a select can be halted. char x = 'x'; (void)write(fd_pipe_quit_read_[1], &x, 1); (void)write(fd_pipe_quit_write_[1], &x, 1); terminated_ = true; } t_recursive_mutex t_connection_table::mtx_connections_; t_connection_table::t_connection_table() : terminated_(false) { create_pipe(fd_pipe_modified_read_); create_pipe(fd_pipe_modified_write_); create_pipe(fd_pipe_quit_read_); create_pipe(fd_pipe_quit_write_); } t_connection_table::~t_connection_table() { t_mutex_guard guard(mtx_connections_); for (list::iterator it = connections_.begin(); it != connections_.end(); ++it) { MEMMAN_DELETE(*it); delete *it; } } void t_connection_table::unlock(void) const { mtx_connections_.unlock(); } bool t_connection_table::empty(void) const { t_mutex_guard guard(mtx_connections_); return connections_.empty(); } t_connection_table::size_type t_connection_table::size(void) const { t_mutex_guard guard(mtx_connections_); return connections_.size(); } void t_connection_table::add_connection(t_connection *connection) { t_mutex_guard guard(mtx_connections_); connections_.push_back(connection); signal_modification_read(); signal_modification_write(); } void t_connection_table::remove_connection(t_connection *connection) { t_mutex_guard guard(mtx_connections_); connections_.remove(connection); signal_modification_read(); signal_modification_write(); } t_connection *t_connection_table::get_connection(unsigned long remote_addr, unsigned short remote_port) { mtx_connections_.lock(); t_connection *found_connection = NULL; list broken_connections; for (list::iterator it = connections_.begin(); it != connections_.end(); ++it) { unsigned long addr; unsigned short port; if ((*it)->may_reuse()) { try { t_socket *socket = (*it)->get_socket(); t_socket_tcp *tcp_socket = dynamic_cast(socket); if (tcp_socket) { tcp_socket->get_remote_address(addr, port); if (addr == remote_addr && port == remote_port) { found_connection = *it; break; } } } catch (int err) { // This should never happen. cerr << "Cannot get remote address of socket." << endl; // Destroy and remove connection as it is probably broken. broken_connections.push_back(*it); } } } // Clear broken connections for (list::iterator it = broken_connections.begin(); it != broken_connections.end(); ++it) { remove_connection(*it); MEMMAN_DELETE(*it); delete *it; } if (!found_connection) mtx_connections_.unlock(); return found_connection; } list t_connection_table::select_read(struct timeval *timeout) const { fd_set read_fds; int nfds = 0; bool retry = true; list result; // Empty modification pipe char pipe_buf; while (read(fd_pipe_modified_read_[0], &pipe_buf, 1) > 0); while (retry) { FD_ZERO(&read_fds); // Add modification pipe so select can be restarted when the // connection table modifies. FD_SET(fd_pipe_modified_read_[0], &read_fds); nfds = fd_pipe_modified_read_[0]; // Add quit pipe so select can quit on demand. FD_SET(fd_pipe_quit_read_[0], &read_fds); nfds = max(nfds, fd_pipe_quit_read_[0]); mtx_connections_.lock(); for (list::const_iterator it = connections_.begin(); it != connections_.end(); ++it) { t_socket *socket = (*it)->get_socket(); int fd = socket->get_descriptor(); FD_SET(fd, &read_fds); nfds = max(nfds, fd); } mtx_connections_.unlock(); int ret = select(nfds + 1, &read_fds, NULL, NULL, timeout); if (ret < 0) throw errno; if (FD_ISSET(fd_pipe_quit_read_[0], &read_fds)) { // Quit was signalled, so stop immediately. break; } mtx_connections_.lock(); // Determine which sockets have become readable for (list::const_iterator it = connections_.begin(); it != connections_.end(); ++it) { t_socket *socket = (*it)->get_socket(); int fd = socket->get_descriptor(); if (FD_ISSET(fd, &read_fds)) { result.push_back(*it); } } if (!result.empty()) { // Connections have become readable, so return to the caller. retry = false; } else { mtx_connections_.unlock(); // No connections have become readable. Check signal descriptors if (FD_ISSET(fd_pipe_modified_read_[0], &read_fds)) { // The connection table is modified. Retry select. read(fd_pipe_modified_read_[0], &pipe_buf, 1); } else { // This should never happen. cerr << "ERROR: select_read returned without any file descriptor." << endl; } } } return result; } list t_connection_table::select_write(struct timeval *timeout) const { fd_set read_fds; fd_set write_fds; int nfds = 0; bool retry = true; list result; // Empty modification pipe char pipe_buf; while (read(fd_pipe_modified_write_[0], &pipe_buf, 1) > 0); while (retry) { FD_ZERO(&read_fds); FD_ZERO(&write_fds); // Add modification pipe so select can be restarted when the // connection table modifies. FD_SET(fd_pipe_modified_write_[0], &read_fds); nfds = fd_pipe_modified_write_[0]; // Add quit pipe so select can quit on demand. FD_SET(fd_pipe_quit_write_[0], &read_fds); nfds = max(nfds, fd_pipe_quit_write_[0]); mtx_connections_.lock(); for (list::const_iterator it = connections_.begin(); it != connections_.end(); ++it) { if ((*it)->has_data_to_send()) { t_socket *socket = (*it)->get_socket(); int fd = socket->get_descriptor(); FD_SET(fd, &write_fds); nfds = max(nfds, fd); } } mtx_connections_.unlock(); int ret = select(nfds + 1, &read_fds, &write_fds, NULL, timeout); if (ret < 0) throw errno; if (FD_ISSET(fd_pipe_quit_write_[0], &read_fds)) { // Quit was signalled, so stop immediately. break; } mtx_connections_.lock(); // Determine which sockets have become writable for (list::const_iterator it = connections_.begin(); it != connections_.end(); ++it) { t_socket *socket = (*it)->get_socket(); int fd = socket->get_descriptor(); if (FD_ISSET(fd, &write_fds)) { result.push_back(*it); } } if (!result.empty()) { // Connections have become writable, so return to the caller. retry = false; } else { mtx_connections_.unlock(); // No connections have become writable. Check signal descriptors if (FD_ISSET(fd_pipe_modified_write_[0], &read_fds)) { // The connection table is modified. Retry select. read(fd_pipe_modified_write_[0], &pipe_buf, 1); } else { // This should never happen. cerr << "ERROR: select_write returned without any file descriptor." << endl; } } } return result; } void t_connection_table::cancel_select(void) { signal_quit(); } void t_connection_table::restart_write_select(void) { signal_modification_write(); } void t_connection_table::close_idle_connections(unsigned long interval, bool &terminated) { t_mutex_guard guard(mtx_connections_); terminated = terminated_; list expired_connections; // Update idle times and find expired connections. for (list::iterator it = connections_.begin(); it != connections_.end(); ++it) { unsigned long idle_time = (*it)->increment_idle_time(interval); if (idle_time >= DUR_IDLE_CONNECTION || terminated) { // If a registered URI is associated with the connection, then // it is persistent and it should not be closed. if (!(*it)->has_registered_uri()) { expired_connections.push_back(*it); } } } // Close expired connections. for (list::iterator it = expired_connections.begin(); it != expired_connections.end(); ++it) { unsigned long ipaddr; unsigned short port; (*it)->get_remote_address(ipaddr, port); log_file->write_header("t_connection_table::close_idle_connections", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Close connection to "); log_file->write_raw(h_ip2str(ipaddr)); log_file->write_raw(":"); log_file->write_raw(int2str(port)); log_file->write_endl(); log_file->write_footer(); remove_connection(*it); MEMMAN_DELETE(*it); delete *it; } } void *connection_timeout_main(void *arg) { bool terminated = false; while (!terminated) { struct timespec sleep_timer; sleep_timer.tv_sec = 1; sleep_timer.tv_nsec = 0; nanosleep(&sleep_timer, NULL); connection_table->close_idle_connections(1000, terminated); } log_file->write_report("Connection timeout handler terminated.", "::connection_timeout_main"); return NULL; }; twinkle-1.10.1/src/sockets/connection_table.h000066400000000000000000000123071277565361200212110ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Connection table */ #ifndef _H_CONNECTION_TABLE #define _H_CONNECTION_TABLE #include #include #include #include #include "connection.h" #include "threads/mutex.h" using namespace std; /** Table of established connections. */ class t_connection_table { private: /** Established connections */ list connections_; /** Mutex to protect concurrent access to the connections. */ static t_recursive_mutex mtx_connections_; /** Indicates if the connection table is terminated. */ bool terminated_; /** Pipe to signal modification of the list of readable connections. */ int fd_pipe_modified_read_[2]; /** Pipe to signal modification of the list of connections with data to send. */ int fd_pipe_modified_write_[2]; /** Pipe to signal the read select operation to quit. */ int fd_pipe_quit_read_[2]; /** Pipe to signal the write select operation to quit. */ int fd_pipe_quit_write_[2]; /** Create a pipe. */ void create_pipe(int p[2]); /** Send a modification signal on the read modification pipe. */ void signal_modification_read(void); /** Send a modification signal on the write modification pipe. */ void signal_modification_write(void); /** Send a quit signal on the modification pipes. */ void signal_quit(void); public: typedef list::size_type size_type; /** Constructor */ t_connection_table(); /** * Destructor. * @note All connections in the table will be closed * and destroyed. */ ~t_connection_table(); /** * Unlock connection table. * @note After some operations, the table stays lock to avoid race * conditions. The caller should unlock the table explicitly * when it has finished working on the connections. */ void unlock(void) const; /** * Check if connection table is empty. * @return true if empty, false if not empty. */ bool empty(void) const; /** * Get number of connections in table. * @return number of connections. */ size_type size(void) const; /** * Add a connection to the table. * @param connection [in] Connection to add. */ void add_connection(t_connection *connection); /** * Remove a TCP connection from the table. * @param connection [in] TCP connection to remove. */ void remove_connection(t_connection *connection); /** * Get a connection to a particular destination. * @param remote_addr [in] IP address of destination. * @param remote_port [in] Port of destination. * @return The connection to the destination. If there is no * connection to the destination, then NULL is returned. * @post If a connection is returned, the table is locked. The caller must * unlock the tbale when it is finished with the connection. * @note Only re-usable connections are considered. */ t_connection *get_connection(unsigned long remote_addr, unsigned short remote_port); /** * Wait for connections to become readable. * @param timeout [in] Maxmimum time to wait. If NULL, then wait indefinitely. * @return List of sockets that are readable. * @throw int Errno * @post The transaction table is locked if a non-empty list of sockets is returned. * @note The caller should unlock the table when processing of the sockets is finished. */ list select_read(struct timeval *timeout) const; /** * Wait for connections to become writeable. * Only connections with data to send are waited for. * @param timeout [in] Maxmimum time to wait. If NULL, then wait indefinitely. * @return List of sockets that are writable. * @throw int Errno * @post The transaction table is locked if a non-empty list of sockets is returned. * @note The caller should unlock the table when processing of the sockets is finished. */ list select_write(struct timeval *timeout) const; /** Cancel all selects. */ void cancel_select(void); /** Restart write select, so new connections with data are picked up. */ void restart_write_select(void); /** * Close all idle connections. * Increment the idle time of all connections with interval. * A persistent connection with associated registered URI's will not be closed. * @param interval [in] Interval to add to idle time (ms). * @param terminated [out] Indicates if the connection table has been terminated. */ void close_idle_connections(unsigned long interval, bool &terminated); }; /** Main for thread handling connection timeouts */ void *connection_timeout_main(void *arg); #endif twinkle-1.10.1/src/sockets/dnssrv.cpp000066400000000000000000000100051277565361200175460ustar00rootroot00000000000000/* This software is copyrighted (c) 2002 Rick van Rein, the Netherlands. This software has been modified by Michel de Boer. 2005 */ #include "dnssrv.h" #include #include #include #include #include #include #include #include #include #include #include #include /* Common offsets into an SRV RR */ #define SRV_COST (RRFIXEDSZ+0) #define SRV_WEIGHT (RRFIXEDSZ+2) #define SRV_PORT (RRFIXEDSZ+4) #define SRV_SERVER (RRFIXEDSZ+6) #define SRV_FIXEDSZ (RRFIXEDSZ+6) /* Data structures */ typedef struct { unsigned char buf [PACKETSZ]; int len; } iobuf; typedef char name [MAXDNAME]; #define MAXNUM_SRV PACKETSZ /* Local variable for SRV options */ static unsigned long int srv_flags = 0L; /* Setup the SRV options when initialising -- invocation optional */ void insrv_init (unsigned long flags) { #ifdef HAVE_RES_INIT srv_flags = flags; res_init (); #endif } /* Test the given SRV options to see if all are set */ int srv_testflag (unsigned long flags) { return ((srv_flags & flags) == flags) ? 1 : 0; } /* Compare two SRV records by priority and by (scattered) weight */ int srvcmp (const void *left, const void *right) { int lcost = ntohs (((unsigned short **) left ) [0][5]); int rcost = ntohs (((unsigned short **) right) [0][5]); if (lcost == rcost) { lcost = -ntohs (((unsigned short **) left ) [0][6]); rcost = -ntohs (((unsigned short **) right) [0][6]); } if (lcost < rcost) { return -1; } else if (lcost > rcost) { return +1; } else { return 0; } } /* Setup a client socket for the named service over the given protocol under * the given domain name. */ int insrv_lookup (const char *service, const char *proto, const char *domain, list &result) { // 1. convert service/proto to svcnm // 2. construct SRV query for _service._proto.domain iobuf names; name svcnm; int ctr; int rnd; HEADER *nameshdr; unsigned char *here, *srv[MAXNUM_SRV]; int num_srv=0; // Storage for fallback SRV list, constructed when DNS gives no SRV //unsigned char fallbacksrv [2*(MAXCDNAME+SRV_FIXEDSZ+MAXCDNAME)]; // srv_flags &= ~SRV_GOT_MASK; // srv_flags |= SRV_GOT_SRV; strcpy (svcnm, "_"); strcat (svcnm, service); strcat (svcnm, "._"); strcat (svcnm, proto); // Note that SRV records are only defined for class IN if (domain) { names.len=res_querydomain (svcnm, domain, C_IN, T_SRV, names.buf, PACKETSZ); } else { names.len=res_query (svcnm, C_IN, T_SRV, names.buf, PACKETSZ); } if (names.len < 0) { return -ENOENT; } nameshdr=(HEADER *) names.buf; here=names.buf + HFIXEDSZ; rnd=nameshdr->id; // Heck, gimme one reason why not! if ((names.len < HFIXEDSZ) || nameshdr->tc) { return -EMSGSIZE; } switch (nameshdr->rcode) { case 1: return -EFAULT; case 2: return -EAGAIN; case 3: return -ENOENT; case 4: return -ENOSYS; case 5: return -EPERM; default: break; } if (ntohs (nameshdr->ancount) == 0) { return -ENOENT; } if (ntohs (nameshdr->ancount) > MAXNUM_SRV) { return -ERANGE; } for (ctr=ntohs (nameshdr->qdcount); ctr>0; ctr--) { int strlen=dn_skipname (here, names.buf+names.len); here += strlen + QFIXEDSZ; } for (ctr=ntohs (nameshdr->ancount); ctr>0; ctr--) { int strlen=dn_skipname (here, names.buf+names.len); here += strlen; srv [num_srv++] = here; here += SRV_FIXEDSZ; here += dn_skipname (here, names.buf+names.len); } // Overwrite weight with rnd-spread version to divide load over weights for (ctr=0; ctr 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, see . */ #ifndef _DNSSRV_H #define _DNSSRV_H #include #include using namespace std; typedef struct { string hostname; unsigned short port; } t_dns_result; void insrv_init (unsigned long flags); int insrv_lookup (const char *service, const char *proto, const char *domain, list &result); #endif twinkle-1.10.1/src/sockets/interfaces.cpp000066400000000000000000000055001277565361200203560ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include "interfaces.h" #include "url.h" using namespace std; t_interface::t_interface(string _name) : name(_name) {} string t_interface::get_ip_addr(void) const { return inet_ntoa(address); } string t_interface::get_ip_netmask(void) const { return inet_ntoa(netmask); } list *get_interfaces(bool include_loopback) { struct ifaddrs *ifa, *ifaddrs; struct sockaddr_in *sin; t_interface *intf; list *result = new list; if (getifaddrs(&ifaddrs)) { // No interfaces found return result; } for (ifa = ifaddrs; ifa ; ifa = ifa -> ifa_next) { // Skip interface without address // Skip interfaces marked DOWN and LOOPBACK. if (ifa->ifa_addr == NULL || !(ifa->ifa_flags & IFF_UP) || ((ifa->ifa_flags & IFF_LOOPBACK) && !include_loopback)) { continue; } // Add the interface to the list if it has an IP4 address switch(ifa->ifa_addr->sa_family) { case AF_INET: intf = new t_interface(ifa->ifa_name); sin = (struct sockaddr_in *)ifa->ifa_addr; memcpy(&intf->address, &sin->sin_addr, sizeof(struct in_addr)); sin = (struct sockaddr_in *)ifa->ifa_netmask; memcpy(&intf->netmask, &sin->sin_addr, sizeof(struct in_addr)); result->push_back(*intf); delete intf; break; } } freeifaddrs(ifaddrs); return result; } bool exists_interface(const string &hostname) { struct hostent *h; h = gethostbyname(hostname.c_str()); if (h == NULL) return false; string ipaddr = inet_ntoa(*((struct in_addr *)h->h_addr)); list *l = get_interfaces(true); for (list::iterator i = l->begin(); i != l->end(); i++) { if (i->get_ip_addr() == ipaddr) { delete l; return true; } } delete l; return false; } bool exists_interface_dev(const string &devname, string &ip_address) { list *l = get_interfaces(true); for (list::iterator i = l->begin(); i != l->end(); i++) { if (i->name == devname) { ip_address = i->get_ip_addr(); delete l; return true; } } delete l; return false; } twinkle-1.10.1/src/sockets/interfaces.h000066400000000000000000000032621277565361200200260ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _H_INTERFACES #define _H_INTERFACES #include #include #include #include #include #include #include #include using namespace std; class t_interface { public: string name; // interface name, eg. eth0 struct in_addr address; // interface IP address struct in_addr netmask; // interface netmask t_interface(string _name); // Get string representation of IP address string get_ip_addr(void) const; string get_ip_netmask(void) const; }; // Return a list of all interfaces that are UP // If include_loopback == true, then the loopback interface is returned as well. list *get_interfaces(bool include_loopback = false); // Check if an interface with a certain IP address exists bool exists_interface(const string &hostname); // Check if an interface exists and return its IP address bool exists_interface_dev(const string &devname, string &ip_address); #endif twinkle-1.10.1/src/sockets/socket.cpp000066400000000000000000000245211277565361200175270ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include "twinkle_config.h" #include "socket.h" #include "audits/memman.h" #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_LINUX_TYPES_H #include #endif #ifdef HAVE_LINUX_ERRQUEUE_H #include #endif ///////////////// // t_icmp_msg ///////////////// t_icmp_msg::t_icmp_msg(short _type, short _code, unsigned long _icmp_src_ipaddr, unsigned long _ipaddr, unsigned short _port) : type(_type), code(_code), icmp_src_ipaddr(_icmp_src_ipaddr), ipaddr(_ipaddr), port(_port) {} ///////////////// // t_socket ///////////////// t_socket::~t_socket() { close(sd); } t_socket::t_socket() : sd(0) {} t_socket::t_socket(int _sd) : sd(_sd) {} int t_socket::get_descriptor(void) const { return sd; } int t_socket::getsockopt(int level, int optname, void *optval, socklen_t *optlen) { return ::getsockopt(sd, level, optname, optval, optlen); } int t_socket::setsockopt(int level, int optname, const void *optval, socklen_t optlen) { return ::setsockopt(sd, level, optname, optval, optlen); } ///////////////// // t_socket_udp ///////////////// t_socket_udp::t_socket_udp() { struct sockaddr_in addr; int ret; sd = socket(AF_INET, SOCK_DGRAM, 0); if (sd < 0) throw errno; addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(0); ret = bind(sd, (struct sockaddr *)&addr, sizeof(addr)); if (ret < 0) throw errno; } t_socket_udp::t_socket_udp(unsigned short port) { struct sockaddr_in addr; int ret; sd = socket(AF_INET, SOCK_DGRAM, 0); if (sd < 0) throw errno; addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(port); ret = bind(sd, (struct sockaddr *)&addr, sizeof(addr)); if (ret < 0) throw errno; } int t_socket_udp::connect(unsigned long dest_addr, unsigned short dest_port) { struct sockaddr_in addr; int ret; addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(dest_addr); addr.sin_port = htons(dest_port); ret = ::connect(sd, (struct sockaddr *)&addr, sizeof(addr)); if (ret < 0) throw errno; return ret; } int t_socket_udp::sendto(unsigned long dest_addr, unsigned short dest_port, const char *data, int data_size) { struct sockaddr_in addr; int ret; addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(dest_addr); addr.sin_port = htons(dest_port); ret = ::sendto(sd, data, data_size, 0, (struct sockaddr *)&addr, sizeof(addr)); if (ret < 0) throw errno; return ret; } ssize_t t_socket_udp::send(const void *data, int data_size) { int ret = ::send(sd, data, data_size, 0); if (ret < 0) throw errno; return ret; } int t_socket_udp::recvfrom(unsigned long &src_addr, unsigned short &src_port, char *buf, int buf_size) { struct sockaddr_in addr; int ret, len_addr; len_addr = sizeof(addr); memset(buf, 0, buf_size); ret = ::recvfrom(sd, buf, buf_size - 1, 0, (struct sockaddr *)&addr, (socklen_t *)&len_addr); if (ret < 0) throw errno; src_addr = ntohl(addr.sin_addr.s_addr); src_port = ntohs(addr.sin_port); return ret; } ssize_t t_socket_udp::recv(void *buf, int buf_size) { int ret; memset(buf, 0, buf_size); ret = ::recv(sd, buf, buf_size - 1, 0); if (ret < 0) throw errno; return ret; } bool t_socket_udp::select_read(unsigned long timeout) { fd_set fds; struct timeval t; FD_ZERO(&fds); FD_SET(sd, &fds); t.tv_sec = timeout / 1000; t.tv_usec = (timeout % 1000) * 1000; int ret = select(sd + 1, &fds, NULL, NULL, &t); if (ret < 0) throw errno; if (ret == 0) return false; return true; } bool t_socket_udp::enable_icmp(void) { #ifdef HAVE_LINUX_ERRQUEUE_H int enable = 1; int ret = setsockopt(SOL_IP, IP_RECVERR, &enable, sizeof(int)); if (ret < 0) return false; return true; #else return false; #endif } bool t_socket_udp::get_icmp(t_icmp_msg &icmp) { #ifdef HAVE_LINUX_ERRQUEUE_H int ret; char buf[256]; // The destination address of the packet causing the ICMP struct sockaddr dest_addr; struct msghdr msgh; struct cmsghdr *cmsg; // Initialize message header to receive the ancillary data for // an ICMP message. memset(&msgh, 0, sizeof(struct msghdr)); msgh.msg_control = buf; msgh.msg_controllen = 256; msgh.msg_name = &dest_addr; msgh.msg_namelen = sizeof(struct sockaddr); // Get error from the socket error queue ret = recvmsg(sd, &msgh, MSG_ERRQUEUE); if (ret < 0) return false; // Find ICMP message in returned controll messages for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; cmsg = CMSG_NXTHDR(&msgh, cmsg)) { if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVERR) { // ICMP message found sock_extended_err *err = (sock_extended_err *)CMSG_DATA(cmsg); icmp.type = err->ee_type; icmp.code = err->ee_code; // Get IP address of host that has sent the ICMP error sockaddr *sa_src_icmp = SO_EE_OFFENDER(err); if (sa_src_icmp->sa_family == AF_INET) { sockaddr_in *addr = (sockaddr_in *)sa_src_icmp; icmp.icmp_src_ipaddr = ntohl(addr->sin_addr.s_addr); } else { // Non supported address type icmp.icmp_src_ipaddr = 0; } // Get destinnation address/port of packet causing the error. if (dest_addr.sa_family == AF_INET) { sockaddr_in *addr = (sockaddr_in *)&dest_addr; icmp.ipaddr = ntohl(addr->sin_addr.s_addr); icmp.port = ntohs(addr->sin_port); return true; } else { // Non supported address type continue; } } } #endif return false; } string h_ip2str(unsigned long ipaddr) { char buf[16]; unsigned long x = htonl(ipaddr); unsigned char *ipbuf = (unsigned char *)&x; snprintf(buf, 16, "%u.%u.%u.%u", ipbuf[0], ipbuf[1], ipbuf[2], ipbuf[3]); return string(buf); } ///////////////// // t_socket_tcp ///////////////// t_socket_tcp::t_socket_tcp() { sd = socket(AF_INET, SOCK_STREAM, 0); if (sd < 0) throw errno; } t_socket_tcp::t_socket_tcp(unsigned short port) { struct sockaddr_in addr; int ret; sd = socket(AF_INET, SOCK_STREAM, 0); if (sd < 0) throw errno; int enable = 1; // Allow server to connect to the socket immediately (disable TIME_WAIT) (void)setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); enable = 1; // Disable Nagle algorithm (void)setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(port); ret = bind(sd, (struct sockaddr *)&addr, sizeof(addr)); if (ret < 0) throw errno; } t_socket_tcp::t_socket_tcp(int _sd) : t_socket(_sd) {} void t_socket_tcp::listen(int backlog) { int ret = ::listen(sd, backlog); if (ret < 0) throw errno; } t_socket_tcp *t_socket_tcp::accept(unsigned long &src_addr, unsigned short &src_port) { struct sockaddr_in addr; socklen_t socklen = sizeof(addr); int ret = ::accept(sd, (struct sockaddr *)&addr, &socklen); if (ret < 0) throw errno; src_addr = ntohl(addr.sin_addr.s_addr); src_port = ntohs(addr.sin_port); t_socket_tcp *sock = new t_socket_tcp(ret); MEMMAN_NEW(sock); return sock; } void t_socket_tcp::connect(unsigned long dest_addr, unsigned short dest_port) { struct sockaddr_in addr; int ret; addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(dest_addr); addr.sin_port = htons(dest_port); ret = ::connect(sd, (struct sockaddr *)&addr, sizeof(addr)); if (ret < 0) throw errno; } ssize_t t_socket_tcp::send(const void *data, int data_size) { ssize_t ret = ::send(sd, data, data_size, 0); if (ret < 0) throw errno; return ret; } ssize_t t_socket_tcp::recv(void *buf, int buf_size) { ssize_t ret; ret = ::recv(sd, buf, buf_size, 0); if (ret < 0) throw errno; return ret; } void t_socket_tcp::get_remote_address(unsigned long &remote_addr, unsigned short &remote_port) { struct sockaddr_in addr; socklen_t namelen = sizeof(addr); int ret = getpeername(sd, (struct sockaddr *)&addr, &namelen); if (ret < 0) throw errno; if (addr.sin_family != AF_INET) throw EBADF; remote_addr = ntohl(addr.sin_addr.s_addr); remote_port = ntohs(addr.sin_port); }; ///////////////// // t_socket_local ///////////////// t_socket_local::t_socket_local() { sd = socket(PF_LOCAL, SOCK_STREAM, 0); if (sd < 0) throw errno; } t_socket_local::t_socket_local(int _sd) { sd = _sd; } void t_socket_local::bind(const string &name) { int ret; struct sockaddr_un sockname; // A name for a local socket can be at most 108 characters // including NULL at end of string. if (name.size() > 107) { throw ENAMETOOLONG; } sockname.sun_family = AF_LOCAL; strcpy(sockname.sun_path, name.c_str()); ret = ::bind(sd, (struct sockaddr *)&sockname, SUN_LEN(&sockname)); if (ret < 0) throw errno; } void t_socket_local::listen(int backlog) { int ret; ret = ::listen(sd, backlog); if (ret < 0) throw errno; } int t_socket_local::accept(void) { int ret; ret = ::accept(sd, NULL, 0); if (ret < 0) throw errno; return ret; } void t_socket_local::connect(const string &name) { int ret; struct sockaddr_un sockname; // A name for a local socket can be at most 108 characters // including NULL at end of string. if (name.size() > 107) { throw ENAMETOOLONG; } sockname.sun_family = AF_LOCAL; strcpy(sockname.sun_path, name.c_str()); ret = ::connect(sd, (struct sockaddr *)&sockname, SUN_LEN(&sockname)); if (ret < 0) throw errno; } int t_socket_local::read(void *buf, int count) { int ret; ret = ::read(sd, buf, count); if (ret < 0) throw errno; return ret; } ssize_t t_socket_local::recv(void *buf, int buf_size) { return read(buf, buf_size); } int t_socket_local::write(const void *buf, int count) { int ret; ret = ::write(sd, buf, count); if (ret < 0) throw errno; return ret; } ssize_t t_socket_local::send(const void *buf, int count) { return write(buf, count); } twinkle-1.10.1/src/sockets/socket.h000066400000000000000000000135161277565361200171760ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Socket operations */ #ifndef _H_SOCKET #define _H_SOCKET #include #include #include #include #include using namespace std; // ports and addresses should be in host order /** ICMP message */ class t_icmp_msg { public: short type; short code; // ICMP source IP address unsigned long icmp_src_ipaddr; // Destination IP address/port of packet causing the ICMP message. unsigned long ipaddr; unsigned short port; t_icmp_msg() {}; t_icmp_msg(short _type, short _code, unsigned long _icmp_src_ipaddr, unsigned long _ipaddr, unsigned short _port); }; /** Abstract socket */ class t_socket { protected: int sd; /**< Socket descriptor. */ /** * Constructor. This constructor does not create a valid socket. * The subclasses will create the real socket. */ t_socket(); /** * Constructor. * @param _sd Socket desciptor. */ t_socket(int _sd); public: /** Destructor */ virtual ~t_socket(); /** * Get the socket descriptor. * @return The socket descriptor. */ int get_descriptor(void) const; /** Get socket options */ int getsockopt(int level, int optname, void *optval, socklen_t *optlen); /** Set socket options */ int setsockopt(int level, int optname, const void *optval, socklen_t optlen); /** Receive data */ virtual ssize_t recv(void *buf, int buf_size) = 0; /** Send data */ virtual ssize_t send(const void *data, int data_size) = 0; }; /** UDP socket */ class t_socket_udp : public t_socket { public: // Create a socket and bind it to any port. // Throws an int exception if it fails. The int thrown is the value // of errno as set by 'socket' or 'bind'. t_socket_udp(); // Create a socket and bind it to port. // Throws an int exception if it fails. The int thrown is the value // of errno as set by 'socket' or 'bind'. t_socket_udp(unsigned short port); // Connect a socket // Throws an int exception if it fails (errno as set by 'sendto') int connect(unsigned long dest_addr, unsigned short dest_port); // Throws an int exception if it fails (errno as set by 'sendto') int sendto(unsigned long dest_addr, unsigned short dest_port, const char *data, int data_size); virtual ssize_t send(const void *data, int data_size); // Throws an int exception if it fails (errno as set by 'recvfrom') // On success the length of the data in buf is returned. After the // data in buf there will be a 0. int recvfrom(unsigned long &src_addr, unsigned short &src_port, char *buf, int buf_size); virtual ssize_t recv(void *buf, int buf_size); // Do a select on the socket in read mode. timeout is in ms. // Returns true if the socket becomes unblocked. Returns false // on time out. Throws an int exception if select fails // (errno as set by 'select') bool select_read(unsigned long timeout); // Enable reception of ICMP errors on this socket. // Returns false if ICMP reception cannot be enabled. bool enable_icmp(void); // Get an ICMP message that was received on this socket. // Returns false if no ICMP message can be retrieved. bool get_icmp(t_icmp_msg &icmp); }; /** TCP socket */ class t_socket_tcp : public t_socket { public: /** * Constructor. Create a socket. * @throw int The errno value */ t_socket_tcp(); /** * Constructor. Create a socket and bind it to a port. * @throw int The errno value */ t_socket_tcp(unsigned short port); /** * Constructor. Create a socket from an existing descriptor. */ t_socket_tcp(int _sd); /** * Listen for a connection. * @throw int Errno */ void listen(int backlog); /** * Accept a connection * @param src_addr [out] Source IPv4 address of the connection. * @param src_port [out] Source port of the connection. * @return A socket for the new connection * @throw int Errno */ t_socket_tcp *accept(unsigned long &src_addr, unsigned short &src_port); /** * Connect to a destination * @param dest_addr [in] Destination IPv4 address. * @param dest_port [in] Destination port. * @throw int Errno */ void connect(unsigned long dest_addr, unsigned short dest_port); /** Send data */ virtual ssize_t send(const void *data, int data_size); /** Receive data */ virtual ssize_t recv(void *buf, int buf_size); /** * Get the remote address of a connection. * @param remote_addr [out] Source IPv4 address of the connection. * @param remote_port [out] Source port of the connection. * @throw int Errno */ void get_remote_address(unsigned long &remote_addr, unsigned short &remote_port); }; /** Local socket */ class t_socket_local : public t_socket { public: // Throws an int exception if it fails. The int thrown is the value // of errno as set by 'socket' t_socket_local(); t_socket_local(int _sd); void bind(const string &name); void listen(int backlog); int accept(void); void connect(const string &name); int read(void *buf, int count); virtual ssize_t recv(void *buf, int buf_size); int write(const void *buf, int count); virtual ssize_t send(const void *buf, int count); }; // Convert an IP address in host order to a string. string h_ip2str(unsigned long ipaddr); #endif twinkle-1.10.1/src/sockets/url.cpp000066400000000000000000000465761277565361200170570ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include #include #include #include #include "dnssrv.h" #include "log.h" #include "socket.h" #include "url.h" #include "user.h" #include "util.h" using namespace std; unsigned short get_default_port(const string &protocol) { if (protocol == "mailto") return 25; if (protocol == "http") return 80; if (protocol == "sip") return 5060; if (protocol == "sips") return 5061; if (protocol == "stun") return 3478; return 0; } unsigned long gethostbyname(const string &name) { struct hostent *h; h = gethostbyname(name.c_str()); if (h == NULL) return 0; return ntohl(*((unsigned long *)h->h_addr)); } list gethostbyname_all(const string &name) { struct hostent *h; list l; h = gethostbyname(name.c_str()); if (h == NULL) return l; char **ipaddr = h->h_addr_list; while (*ipaddr) { l.push_back(ntohl(*((unsigned long *)(*ipaddr)))); ipaddr++; } return l; } string get_local_hostname(void) { char name[256]; int rc = gethostname(name, 256); if (rc < 0) { return "localhost"; } struct hostent *h = gethostbyname(name); if (h == NULL) { return "localhost"; } return h->h_name; } unsigned long get_src_ip4_address_for_dst(unsigned long dst_ip4) { string log_msg; struct sockaddr_in addr; int ret; // Create UDP socket int sd = socket(AF_INET, SOCK_DGRAM, 0); if (sd < 0) { string err = get_error_str(errno); log_msg = "Cannot create socket: "; log_msg += err; log_file->write_report(log_msg, "::get_src_ip4_address_for_dst", LOG_NORMAL, LOG_CRITICAL); return 0; } memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(dst_ip4); addr.sin_port = htons(5060); // Connect to the destination. Note that no network traffic // is sent out as this is a UDP socket. The routing engine // will set the correct source address however. ret = connect(sd, (struct sockaddr *)&addr, sizeof(addr)); if (ret < 0) { string err = get_error_str(errno); log_msg = "Cannot connect socket: "; log_msg += err; log_file->write_report(log_msg, "::get_src_ip4_address_for_dst", LOG_NORMAL, LOG_CRITICAL); close(sd); return 0; } // Get source address of socket memset(&addr, 0, sizeof(addr)); socklen_t len_addr = sizeof(addr); ret = getsockname(sd, (struct sockaddr *)&addr, &len_addr); if (ret < 0) { string err = get_error_str(errno); log_msg = "Cannot get sockname: "; log_msg += err; log_file->write_report(log_msg, "::get_src_ip4_address_for_dst", LOG_NORMAL, LOG_CRITICAL); close(sd); return 0; } close(sd); return ntohl(addr.sin_addr.s_addr); } string display_and_url2str(const string &display, const string &url) { string s; if (!display.empty()) { if (must_quote(display)) s += '"'; s += display; if (must_quote(display)) s += '"'; s += " <"; } s += url; if (!display.empty()) s += '>'; return s; } // t_ip_port t_ip_port::t_ip_port(unsigned long _ipaddr, unsigned short _port) : transport("udp"), ipaddr(_ipaddr), port(_port) {} t_ip_port::t_ip_port(const string &proto, unsigned long _ipaddr, unsigned short _port) : transport(proto), ipaddr(_ipaddr), port(_port) {} void t_ip_port::clear(void) { transport.clear(); ipaddr = 0; port = 0; } bool t_ip_port::is_null(void) const { return (ipaddr == 0 && port == 0); } bool t_ip_port::operator==(const t_ip_port &other) const { return (transport == other.transport && ipaddr == other.ipaddr && port == other.port); } bool t_ip_port::operator!=(const t_ip_port &other) const { return !operator==(other); } string t_ip_port::tostring(void) const { string s; s = transport; s += ":"; s += h_ip2str(ipaddr); s += ":"; s += int2str(port); return s; } // Private void t_url::construct_user_url(const string &s) { string::size_type i; string r; // Determine user/password (both are optional) i = s.find('@'); if (i != string::npos) { if (i == 0 || i == s.size()-1) return; string userpass = s.substr(0, i); r = s.substr(i+1); i = userpass.find(':'); if (i != string::npos) { if (i == 0 || i == userpass.size()-1) return; user = unescape_hex(userpass.substr(0, i)); password = unescape_hex(userpass.substr(i+1)); if (escape_passwd_value(password) != password) { modified = true; } } else { user = unescape_hex(userpass); } // Set modified flag if user contains reserved symbols. // This enforces escaping these symbols when the url gets // encoded. if (escape_user_value(user) != user) { modified = true; } } else { r = s; } // Determine host/port string hostport; i = r.find_first_of(";?"); if (i != string::npos) { hostport = r.substr(0, i); if (!parse_params_headers(r.substr(i))) return; } else { hostport = r; } if (hostport.empty()) return; if (hostport.at(0) == '[') { // Host contains an IPv6 reference i = hostport.find(']'); if (i == string::npos) return; // TODO: check format of an IPv6 address host = hostport.substr(0, i+1); if (i < hostport.size()-1) { if (hostport.at(i+1) != ':') return; // wrong port separator if (i+1 == hostport.size()-1) return; // port missing unsigned long p = atol(hostport.substr(i+2).c_str()); if (p > 65535) return; // illegal port value port = (unsigned short)p; } } else { // Host contains a host name or IPv4 address i = hostport.find(':'); if (i != string::npos) { if (i == 0 || i == hostport.size()-1) return; host = hostport.substr(0, i); unsigned long p = atol(hostport.substr(i+1).c_str()); if (p > 65535) return; // illegal port value port = (unsigned short)p; } else { host = hostport; } } user_url = true; valid = true; } void t_url::construct_machine_url(const string &s) { string::size_type i; // Determine host string hostport; i = s.find_first_of("/?;"); if ( i != string::npos) { hostport = s.substr(0, i); if (!parse_params_headers(s.substr(i))) return; } else { hostport = s; } if (hostport.empty()) return; if (hostport.at(0) == '[') { // Host contains an IPv6 reference i = hostport.find(']'); if (i == string::npos) return; // TODO: check format of an IPv6 address host = hostport.substr(0, i+1); if (i < hostport.size()-1) { if (hostport.at(i+1) != ':') return; // wrong port separator if (i+1 == hostport.size()-1) return; // port missing unsigned long p = atol(hostport.substr(i+2).c_str()); if (p > 65535) return; // illegal port value port = (unsigned short)p; } } else { // Host contains a host name or IPv4 address i = hostport.find(':'); if (i != string::npos) { if (i == 0 || i == hostport.size()-1) return; host = hostport.substr(0, i); unsigned long p = atol(hostport.substr(i+1).c_str()); if (p > 65535) return; // illegal port value port = (unsigned short)p; } else { host = hostport; } } user_url = false; valid = true; } bool t_url::parse_params_headers(const string &s) { string param_str = ""; // Find start of headers // Note: parameters will not contain / or ?-symbol string::size_type header_start = s.find_first_of("/?"); if (header_start != string::npos) { headers = s.substr(header_start + 1); if (s[0] == ';') { // The first symbol of the parameter list is ; // Remove this. param_str = s.substr(1, header_start - 1); } } else { // There are no headers // The first symbol of the parameter list is ; // Remove this. param_str = s.substr(1); } if (param_str == "") return true; // Create a list of single parameters. Parameters are // seperated by semi-colons. // Note: parameters will not contain a semi-colon in the // name or value. vector param_lst = split(param_str, ';'); // Parse the parameters for (vector::iterator i = param_lst.begin(); i != param_lst.end(); i++) { string pname; string pvalue; vector param = split(*i, '='); if (param.size() > 2) return false; pname = tolower(unescape_hex(trim(param.front()))); if (param.size() == 2) { pvalue = tolower(unescape_hex(trim(param.back()))); } if (pname == "transport") { transport = pvalue; } else if (pname == "maddr") { maddr = pvalue; } else if (pname == "lr") { lr = true; } else if (pname == "user") { user_param = pvalue; } else if (pname == "method") { method = pvalue; } else if (pname == "ttl") { ttl = atoi(pvalue.c_str()); } else { other_params += ';'; other_params += *i; } } return true; } // Public static string t_url::escape_user_value(const string &user_value) { // RFC 3261 // user = 1*( unreserved / escaped / user-unreserved ) // user-unreserved = "&" / "=" / "+" / "$" / "," / ";" / "?" / "/" // unreserved = alphanum / mark // mark = "-" / "_" / "." / "!" / "~" / "*" / "'" / "(" / ")" return escape_hex(user_value, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYX0123456789"\ "-_.!~*'()&=+$,;?/"); } string t_url::escape_passwd_value(const string &passwd_value) { // RFC 3261 // password = *( unreserved / escaped / "&" / "=" / "+" / "$" / "," ) // unreserved = alphanum / mark // mark = "-" / "_" / "." / "!" / "~" / "*" / "'" / "(" / ")" return escape_hex(passwd_value, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYX0123456789"\ "-_.!~*'()&=+$,"); } string t_url::escape_hnv(const string &hnv) { // RFC 3261 // hname = 1*( hnv-unreserved / unreserved / escaped ) // hvalue = *( hnv-unreserved / unreserved / escaped ) // hnv-unreserved = "[" / "]" / "/" / "?" / ":" / "+" / "$" // unreserved = alphanum / mark // mark = "-" / "_" / "." / "!" / "~" / "*" / "'" / "(" / ")" return escape_hex(hnv, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYX0123456789"\ "-_.!~*'()[]/?:+$"); } // Public t_url::t_url(void) { modified = false; valid = false; port = 0; lr = false; ttl = 0; } t_url::t_url(const string &s) { set_url(s); } t_url t_url::copy_without_headers(void) const { t_url u(*this); u.clear_headers(); return u; } void t_url::set_url(const string &s) { string::size_type i; string r; modified = false; valid = false; scheme.clear(); user.clear(); password.clear(); host.clear(); port = 0; transport.clear(); maddr.clear(); lr = false; user_param.clear(); method.clear(); ttl = 0; other_params.clear(); headers.clear(); user_url = false; text_format = s; // Determine scheme. A scheme is mandatory. There should // be text following the scheme. i = s.find(':'); if (i == string::npos || i == 0 || i == s.size()-1) return; scheme = tolower(s.substr(0, i)); r = s.substr(i+1); if (r[0] == '/') { if (r.size() == 1) return; if (r[1] != '/') return; construct_machine_url(r.substr(2)); } else { construct_user_url(r); } } string t_url::get_scheme(void) const { return scheme; } string t_url::get_user(void) const { return user; } string t_url::get_password(void) const { return password; } string t_url::get_host(void) const { return host; } int t_url::get_nport(void) const { return htons(get_hport()); } int t_url::get_hport(void) const { if (port != 0) return port; return get_default_port(scheme); } int t_url::get_port(void) const { return port; } unsigned long t_url::get_n_ip(void) const { struct hostent *h; // TODO: handle multiple A RR's if (scheme == "tel") return 0; h = gethostbyname(host.c_str()); if (h == NULL) return 0; return *((unsigned long *)h->h_addr); } unsigned long t_url::get_h_ip(void) const { if (scheme == "tel") return 0; return gethostbyname(host); } list t_url::get_h_ip_all(void) const { if (scheme == "tel") return list(); return gethostbyname_all(host); } string t_url::get_ip(void) const { struct hostent *h; // TODO: handle multiple A RR's if (scheme == "tel") return 0; h = gethostbyname(host.c_str()); if (h == NULL) return ""; return inet_ntoa(*((struct in_addr *)h->h_addr)); } list t_url::get_h_ip_srv(const string &transport) const { list ip_list; list srv_list; list ipaddr_list; if (scheme == "tel") return list(); // RFC 3263 4.2 // Only do an SRV lookup if host is a hostname and no port is specified. if (!is_ipaddr(host) && port == 0) { int ret = insrv_lookup(scheme.c_str(), transport.c_str(), host.c_str(), srv_list); if (ret >= 0 && !srv_list.empty()) { // SRV RR's found for (list::iterator i = srv_list.begin(); i != srv_list.end(); i++) { // Get A RR's t_ip_port ip_port; ipaddr_list = gethostbyname_all(i->hostname); for (list::iterator j = ipaddr_list.begin(); j != ipaddr_list.end(); j++) { ip_list.push_back(t_ip_port(transport, *j, i->port)); } } return ip_list; } } // No SRV RR's found, do an A RR lookup t_ip_port ip_port; ipaddr_list = get_h_ip_all(); for (list::iterator j = ipaddr_list.begin(); j != ipaddr_list.end(); j++) { ip_list.push_back(t_ip_port(transport, *j, get_hport())); } return ip_list; } string t_url::get_transport(void) const { return transport; } string t_url::get_maddr(void) const { return maddr; } bool t_url::get_lr(void) const { return lr; } string t_url::get_user_param(void) const { return user_param; } string t_url::get_method(void) const { return method; } int t_url::get_ttl(void) const { return ttl; } string t_url::get_other_params(void) const { return other_params; } string t_url::get_headers(void) const { return headers; } void t_url::set_user(const string &u) { modified = true; user = u; } void t_url::set_host(const string &h) { modified = true; host = h; } void t_url::add_header(const t_header &hdr) { if (!hdr.is_populated()) return; modified = true; if (!headers.empty()) headers += ';'; headers += escape_hnv(hdr.get_name()); headers += '='; headers += escape_hnv(hdr.get_value()); } void t_url::clear_headers(void) { if (headers.empty()) { // No headers to clear return; } modified = true; headers.clear(); } bool t_url::is_valid(void) const { return valid; } // RCF 3261 19.1.4 bool t_url::sip_match(const t_url &u) const { if (!u.is_valid() || !is_valid()) return false; // Compare schemes if (scheme != "sip" && scheme != "sips") return false; if (u.get_scheme() != "sip" && u.get_scheme() != "sips") { return false; } if (scheme != u.get_scheme()) return false; // Compare user info if (user != u.get_user()) return false; if (password != u.get_password()) return false; // Compare host/port if (cmp_nocase(host, u.get_host()) != 0) return false; if (port != u.get_port()) return false; // Compare parameters if (transport != "" && u.get_transport() != "" && cmp_nocase(transport, u.get_transport()) != 0) { return false; } if (maddr != u.get_maddr()) return false; if (cmp_nocase(user_param, u.get_user_param()) != 0) return false; if (cmp_nocase(method, u.get_method()) != 0) return false; if (ttl != u.get_ttl()) return false; // TODO: compare other params and headers return true; } bool t_url::operator==(const t_url &u) const { return sip_match(u); } bool t_url::operator!=(const t_url &u) const { return !sip_match(u); } bool t_url::user_host_match(const t_url &u, bool looks_like_phone, const string &special_symbols) const { string u1 = get_user(); string u2 = u.get_user(); // For tel-URIs the phone number is in the host part. if (scheme == "tel") u1 = get_host(); if (u.scheme == "tel") u2 = u.get_host(); bool u1_is_phone = false; bool u2_is_phone = false; if (is_phone(looks_like_phone, special_symbols)) { u1 = remove_symbols(u1, special_symbols); u1_is_phone = true; } if (u.is_phone(looks_like_phone, special_symbols)) { u2 = remove_symbols(u2, special_symbols); u2_is_phone = true; } if (u1 != u2) return false; if (u1_is_phone && u2_is_phone) { // Both URLs are phone numbers. Do not compare // the host-part. return true; } return (get_host() == u.get_host()); } bool t_url::user_looks_like_phone(const string &special_symbols) const { return looks_like_phone(user, special_symbols); } bool t_url::is_phone(bool looks_like_phone, const string &special_symbols) const { if (scheme == "tel") return true; // RFC 3261 19.1.1 if (user_param == "phone") return true; return (looks_like_phone && user_looks_like_phone(special_symbols)); } string t_url::encode(void) const { if (modified) { if (!user_url) { // TODO: machine URL's are currently not used return text_format; } string s; s = scheme; s += ':'; if (!user.empty()) { s += escape_user_value(user); if (!password.empty()) { s += ':'; s += escape_passwd_value(password); } s += '@'; } s += host; if (port > 0) { s += ':'; s += int2str(port); } if (!transport.empty()) { s += ";transport="; s += transport; } if (!maddr.empty()) { s += ";maddr="; s += maddr; } if (lr) { s += ";lr"; } if (!user_param.empty()) { s += ";user="; s += user_param; } if (!method.empty()) { s += ";method="; s += method; } if (ttl > 0) { s += ";ttl="; s += int2str(ttl); } if (!other_params.empty()) { s += other_params; } if (!headers.empty()) { s += "?"; s += headers; } return s; } else { return text_format; } } string t_url::encode_noscheme(void) const { string s = encode(); string::size_type i = s.find(':'); if (i != string::npos && i < s.size()) { s = s.substr(i + 1); } return s; } string t_url::encode_no_params_hdrs(bool escape) const { if (!user_url) { // TODO: machine URL's are currently not used return text_format; } string s; s = scheme; s += ':'; if (!user.empty()) { if (escape) { s += escape_user_value(user); } else { s += user; } if (!password.empty()) { s += ':'; if (escape) { s += escape_passwd_value(password); } else { s += password; } } s += '@'; } s += host; if (port > 0) { s += ':'; s += int2str(port); } return s; } void t_url::apply_conversion_rules(t_user *user_config) { if (scheme == "tel") { host = user_config->convert_number(host); } else { // Convert user part for all other schemes user = user_config->convert_number(user); } modified = true; } t_display_url::t_display_url() {} t_display_url::t_display_url(const t_url &_url, const string &_display) : url(_url), display(_display) {} bool t_display_url::is_valid() { return url.is_valid(); } string t_display_url::encode(void) const { string s; if (!display.empty()) { if (must_quote(display)) s += '"'; s += display; if (must_quote(display)) s += '"'; s += " <"; } s += url.encode(); if (!display.empty()) s += '>'; return s; } twinkle-1.10.1/src/sockets/url.h000066400000000000000000000173151277565361200165110ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _H_URL #define _H_URL #include #include #include "parser/header.h" /** @name Forward declarations */ //@{ class t_user; //@} using namespace std; class t_ip_port { public: string transport; unsigned long ipaddr; unsigned short port; t_ip_port() : transport("udp") {}; t_ip_port(unsigned long _ipaddr, unsigned short _port); t_ip_port(const string &proto, unsigned long _ipaddr, unsigned short _port); void clear(void); bool is_null(void) const; bool operator==(const t_ip_port &other) const; bool operator!=(const t_ip_port &other) const; string tostring(void) const; }; // Return the default port for a protocol (host order) unsigned short get_default_port(const string &protocol); // Return the first IP address of host name. // Return 0 if no IP address can be found. unsigned long gethostbyname(const string &name); // Return all IP address of host name list gethostbyname_all(const string &name); /** * Get local host name. * @return Local host name. */ string get_local_hostname(void); /** * Get the source IP address that will be used for sending * a packet to a certain destination. * @param dst_ip4 [in] The destination IPv4 address. * @return The source IPv4 address. * @return 0 if the source address cannot be determined. */ unsigned long get_src_ip4_address_for_dst(unsigned long dst_ip4); class t_url { private: /** * A t_url object is created with a string represnetation of * the URL. The encode method just returns this string. * If one of the components of the t_url object is modified * however, then encode will build a new string representation. * The modified flag indicates if the object was modified after * construction. */ bool modified; /** URL scheme. */ string scheme; /** The user part of a URL. For a tel URL this is empty. */ string user; /** The user password. */ string password; /** * The host part of a URL. For a tel URL, it contains the part before * the first semi-colon. */ string host; unsigned short port; // host order // parameters string transport; string maddr; bool lr; string user_param; string method; int ttl; string other_params; // unparsed other parameters // starting with a semi-colon // headers string headers; // unparsed headers bool user_url; // true -> user url // false -> machine bool valid; // indicates if the url is valid string text_format; // url in string format void construct_user_url(const string &s); // eg sip:, mailto: void construct_machine_url(const string &s); // eg http:, ftp: // Parse uri parameters and headers. Returns false if parsing // fails. bool parse_params_headers(const string &s); public: // Escape reserved symbols in a user value static string escape_user_value(const string &user_value); // Escape reserved symbols in a password value static string escape_passwd_value(const string &passwd_value); // Escape reserved symbols in a header name or value static string escape_hnv(const string &hnv); public: t_url(); t_url(const string &s); // Return a copy of the URI without headers. // If the URI does not contain any headers, then the copy is // identical to the URI. t_url copy_without_headers(void) const; void set_url(const string &s); // Returns "" or 0 if item cannot be found string get_scheme(void) const; string get_user(void) const; string get_password(void) const; string get_host(void) const; // The following methods will return the default port if // no port is present in the url. int get_nport(void) const; // Get port in network order. int get_hport(void) const; // get port in host order. // The following method returns 0 if no port is present // in the url. int get_port(void) const; // ip address network order. Return 0 if address not found // DNS A RR lookup unsigned long get_n_ip(void) const; // ip address host order. Return 0 if address not found // DNS A RR lookup unsigned long get_h_ip(void) const; list get_h_ip_all(void) const; // DNS A RR lookup string get_ip(void) const; // ip address as string // Get list op IP address/ports in host order. // First do DNS SRV lookup. If no SRV RR's are found, then // do a DNS A RR lookup. // transport = the transport protocol for the service list get_h_ip_srv(const string &transport) const; /** @name Getters */ //@{ string get_transport(void) const; string get_maddr(void) const; bool get_lr(void) const; string get_user_param(void) const; string get_method(void) const; int get_ttl(void) const; string get_other_params(void) const; string get_headers(void) const; //@} /** @name Setters */ //@{ void set_user(const string &u); void set_host(const string &h); //@} /** * Add a header to the URI. * The encoded header will be concatenated to the headers field. * @param hdr [in] Header to be added. */ void add_header(const t_header &hdr); /** Remove headers from the URI. */ void clear_headers(void); /** * Check if the URI is valid. * @return True if valid, otherwise false. */ bool is_valid(void) const; // Check if 2 sip or sips url's are equivalent bool sip_match(const t_url &u) const; bool operator==(const t_url &u) const; bool operator!=(const t_url &u) const; /** * Check if the user-host part of 2 url's are equal. * If the user-part is a phone number, then only compare * the user parts. If the url is a tel-url then the host * contains a telephone number for comparison. * @param u [in] Other URL to compare with. * @param looks_like_phone [in] Flag to indicate is a SIP URL * that looks like a phone number must be treated as such. * @param special_symbols [in] Interpuction symbols in a phone number. * @return true if the URLs match, false otherwise. */ bool user_host_match(const t_url &u, bool looks_like_phone, const string &special_symbols) const; // Return true if the user part looks like a phone number, i.e. // consists of digits, *, # and special symbols bool user_looks_like_phone(const string &special_symbols) const; // Return true if the URI indicates a phone number, i.e. // - the user=phone parameter is present // or // - if looks_like_phone == true and the user part looks like // a phone number bool is_phone(bool looks_like_phone, const string &special_symbols) const; // Return string encoding of url string encode(void) const; // Return string encoding of url without scheme information string encode_noscheme(void) const; // Return string encoding of url without parameters/headers string encode_no_params_hdrs(bool escape = true) const; /** * Apply number conversion rules to modify the URL. * @param user_config [in] The user profile having the conversion * rules to apply. */ void apply_conversion_rules(t_user *user_config); }; // Display name and url combined class t_display_url { public: t_url url; string display; t_display_url(); t_display_url(const t_url &_url, const string &_display); bool is_valid(); string encode(void) const; }; #endif twinkle-1.10.1/src/stun/000077500000000000000000000000001277565361200150455ustar00rootroot00000000000000twinkle-1.10.1/src/stun/CMakeLists.txt000066400000000000000000000002331277565361200176030ustar00rootroot00000000000000project(libtwinkle-stun) set(LIBTWINKLE_STUN-SRCS stun.cxx stun_transaction.cpp udp.cxx ) add_library(libtwinkle-stun OBJECT ${LIBTWINKLE_STUN-SRCS}) twinkle-1.10.1/src/stun/stun.cxx000066400000000000000000002063611277565361200165720ustar00rootroot00000000000000#include #include #include #include #include #ifdef WIN32 #include #include #include #include #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif #if defined(__sparc__) || defined(WIN32) #define NOSSL #endif #define NOSSL #include "udp.h" #include "stun.h" // Twinkle #include "util.h" #include "sockets/socket.h" #include "audits/memman.h" using namespace std; static void computeHmac(char* hmac, const char* input, int length, const char* key, int keySize); static bool stunParseAtrAddress( char* body, unsigned int hdrLen, StunAtrAddress4& result ) { if ( hdrLen != 8 ) { clog << "hdrLen wrong for Address" <= sizeof(result) ) { clog << "head on Error too large" << endl; return false; } else { memcpy(&result.pad, body, 2); body+=2; result.pad = ntohs(result.pad); result.errorClass = *body++; result.number = *body++; result.sizeReason = hdrLen - 4; memcpy(&result.reason, body, result.sizeReason); result.reason[result.sizeReason] = 0; return true; } } static bool stunParseAtrUnknown( char* body, unsigned int hdrLen, StunAtrUnknown& result ) { if ( hdrLen >= sizeof(result) ) { return false; } else { if (hdrLen % 4 != 0) return false; result.numAttributes = hdrLen / 4; for (int i=0; i= STUN_MAX_STRING ) { clog << "String is too large" << endl; return false; } else { if (hdrLen % 4 != 0) { clog << "Bad length string " << hdrLen << endl; return false; } result.sizeValue = hdrLen; memcpy(&result.value, body, hdrLen); result.value[hdrLen] = 0; return true; } } static bool stunParseAtrIntegrity( char* body, unsigned int hdrLen, StunAtrIntegrity& result ) { if ( hdrLen != 20) { clog << "MessageIntegrity must be 20 bytes" << endl; return false; } else { memcpy(&result.hash, body, hdrLen); return true; } } bool stunParseMessage( char* buf, unsigned int bufLen, StunMessage& msg, bool verbose) { if (verbose) clog << "Received stun message: " << bufLen << " bytes" << endl; memset(&msg, 0, sizeof(msg)); if (sizeof(StunMsgHdr) > bufLen) { return false; } memcpy(&msg.msgHdr, buf, sizeof(StunMsgHdr)); msg.msgHdr.msgType = ntohs(msg.msgHdr.msgType); msg.msgHdr.msgLength = ntohs(msg.msgHdr.msgLength); if (msg.msgHdr.msgLength + sizeof(StunMsgHdr) != bufLen) { clog << "Message header length doesn't match message size: " << msg.msgHdr.msgLength << " - " << bufLen << endl; return false; } char* body = buf + sizeof(StunMsgHdr); unsigned int size = msg.msgHdr.msgLength; //clog << "bytes after header = " << size << endl; while ( size > 0 ) { // !jf! should check that there are enough bytes left in the buffer StunAtrHdr* attr = reinterpret_cast(body); unsigned int attrLen = ntohs(attr->length); int atrType = ntohs(attr->type); //if (verbose) clog << "Found attribute type=" << AttrNames[atrType] << " length=" << attrLen << endl; if ( attrLen+4 > size ) { clog << "claims attribute is larger than size of message " <<"(attribute type="<(&ndata), sizeof(UInt16)); return buf + sizeof(UInt16); } static char* encode32(char* buf, UInt32 data) { UInt32 ndata = htonl(data); memcpy(buf, reinterpret_cast(&ndata), sizeof(UInt32)); return buf + sizeof(UInt32); } static char* encode(char* buf, const char* data, unsigned int length) { memcpy(buf, data, length); return buf + length; } static char* encodeAtrAddress4(char* ptr, UInt16 type, const StunAtrAddress4& atr) { ptr = encode16(ptr, type); ptr = encode16(ptr, 8); *ptr++ = atr.pad; *ptr++ = IPv4Family; ptr = encode16(ptr, atr.ipv4.port); ptr = encode32(ptr, atr.ipv4.addr); return ptr; } static char* encodeAtrChangeRequest(char* ptr, const StunAtrChangeRequest& atr) { ptr = encode16(ptr, ChangeRequest); ptr = encode16(ptr, 4); ptr = encode32(ptr, atr.value); return ptr; } static char* encodeAtrError(char* ptr, const StunAtrError& atr) { ptr = encode16(ptr, ErrorCode); ptr = encode16(ptr, 6 + atr.sizeReason); ptr = encode16(ptr, atr.pad); *ptr++ = atr.errorClass; *ptr++ = atr.number; ptr = encode(ptr, atr.reason, atr.sizeReason); return ptr; } static char* encodeAtrUnknown(char* ptr, const StunAtrUnknown& atr) { ptr = encode16(ptr, UnknownAttribute); ptr = encode16(ptr, 2+2*atr.numAttributes); for (int i=0; i= sizeof(StunMsgHdr)); char* ptr = buf; ptr = encode16(ptr, msg.msgHdr.msgType); char* lengthp = ptr; ptr = encode16(ptr, 0); ptr = encode(ptr, reinterpret_cast(msg.msgHdr.id.octet), sizeof(msg.msgHdr.id)); if (verbose) clog << "Encoding stun message: " << endl; if (msg.hasMappedAddress) { if (verbose) clog << "Encoding MappedAddress: " << msg.mappedAddress.ipv4 << endl; ptr = encodeAtrAddress4 (ptr, MappedAddress, msg.mappedAddress); } if (msg.hasResponseAddress) { if (verbose) clog << "Encoding ResponseAddress: " << msg.responseAddress.ipv4 << endl; ptr = encodeAtrAddress4(ptr, ResponseAddress, msg.responseAddress); } if (msg.hasChangeRequest) { if (verbose) clog << "Encoding ChangeRequest: " << msg.changeRequest.value << endl; ptr = encodeAtrChangeRequest(ptr, msg.changeRequest); } if (msg.hasSourceAddress) { if (verbose) clog << "Encoding SourceAddress: " << msg.sourceAddress.ipv4 << endl; ptr = encodeAtrAddress4(ptr, SourceAddress, msg.sourceAddress); } if (msg.hasChangedAddress) { if (verbose) clog << "Encoding ChangedAddress: " << msg.changedAddress.ipv4 << endl; ptr = encodeAtrAddress4(ptr, ChangedAddress, msg.changedAddress); } if (msg.hasUsername) { if (verbose) clog << "Encoding Username: " << msg.username.value << endl; ptr = encodeAtrString(ptr, Username, msg.username); } if (msg.hasPassword) { if (verbose) clog << "Encoding Password: " << msg.password.value << endl; ptr = encodeAtrString(ptr, Password, msg.password); } if (msg.hasErrorCode) { if (verbose) clog << "Encoding ErrorCode: class=" << int(msg.errorCode.errorClass) << " number=" << int(msg.errorCode.number) << " reason=" << msg.errorCode.reason << endl; ptr = encodeAtrError(ptr, msg.errorCode); } if (msg.hasUnknownAttributes) { if (verbose) clog << "Encoding UnknownAttribute: ???" << endl; ptr = encodeAtrUnknown(ptr, msg.unknownAttributes); } if (msg.hasReflectedFrom) { if (verbose) clog << "Encoding ReflectedFrom: " << msg.reflectedFrom.ipv4 << endl; ptr = encodeAtrAddress4(ptr, ReflectedFrom, msg.reflectedFrom); } if (msg.hasXorMappedAddress) { if (verbose) clog << "Encoding XorMappedAddress: " << msg.xorMappedAddress.ipv4 << endl; ptr = encodeAtrAddress4 (ptr, XorMappedAddress, msg.xorMappedAddress); } if (msg.xorOnly) { if (verbose) clog << "Encoding xorOnly: " << endl; ptr = encodeXorOnly( ptr ); } if (msg.hasServerName) { if (verbose) clog << "Encoding ServerName: " << msg.serverName.value << endl; ptr = encodeAtrString(ptr, ServerName, msg.serverName); } if (msg.hasSecondaryAddress) { if (verbose) clog << "Encoding SecondaryAddress: " << msg.secondaryAddress.ipv4 << endl; ptr = encodeAtrAddress4 (ptr, SecondaryAddress, msg.secondaryAddress); } if (password.sizeValue > 0) { if (verbose) clog << "HMAC with password: " << password.value << endl; StunAtrIntegrity integrity; computeHmac(integrity.hash, buf, int(ptr-buf) , password.value, password.sizeValue); ptr = encodeAtrIntegrity(ptr, integrity); } if (verbose) clog << endl; encode16(lengthp, UInt16(ptr - buf - sizeof(StunMsgHdr))); return int(ptr - buf); } int stunRand() { // return 32 bits of random stuff assert( sizeof(int) == 4 ); static bool init=false; if ( !init ) { init = true; UInt64 tick; // Twinkle: removed platform dependent code (except WIN32 code) #if defined(WIN32) volatile unsigned int lowtick=0,hightick=0; __asm { rdtsc mov lowtick, eax mov hightick, edx } tick = hightick; tick <<= 32; tick |= lowtick; #else tick = time(NULL); #endif int seed = int(tick); #ifdef WIN32 srand(seed); #else srandom(seed); #endif } #ifdef WIN32 assert( RAND_MAX == 0x7fff ); int r1 = rand(); int r2 = rand(); int ret = (r1<<16) + r2; return ret; #else return random(); #endif } /// return a random number to use as a port static int randomPort() { int min=0x4000; int max=0x7FFF; int ret = stunRand(); ret = ret|min; ret = ret&max; return ret; } #ifdef NOSSL static void computeHmac(char* hmac, const char* input, int length, const char* key, int sizeKey) { strncpy(hmac,"hmac-not-implemented",20); } #else #include static void computeHmac(char* hmac, const char* input, int length, const char* key, int sizeKey) { unsigned int resultSize=0; HMAC(EVP_sha1(), key, sizeKey, reinterpret_cast(input), length, reinterpret_cast(hmac), &resultSize); assert(resultSize == 20); } #endif static void toHex(const char* buffer, int bufferSize, char* output) { static char hexmap[] = "0123456789abcdef"; const char* p = buffer; char* r = output; for (int i=0; i < bufferSize; i++) { unsigned char temp = *p++; int hi = (temp & 0xf0)>>4; int low = (temp & 0xf); *r++ = hexmap[hi]; *r++ = hexmap[low]; } *r = 0; } void stunCreateUserName(const StunAddress4& source, StunAtrString* username) { UInt64 time = stunGetSystemTimeSecs(); time -= (time % 20*60); //UInt64 hitime = time >> 32; UInt64 lotime = time & 0xFFFFFFFF; char buffer[1024]; sprintf(buffer, "%08x:%08x:%08x:", UInt32(source.addr), UInt32(stunRand()), UInt32(lotime)); assert( strlen(buffer) < 1024 ); assert(strlen(buffer) + 41 < STUN_MAX_STRING); char hmac[20]; char key[] = "Jason"; computeHmac(hmac, buffer, strlen(buffer), key, strlen(key) ); char hmacHex[41]; toHex(hmac, 20, hmacHex ); hmacHex[40] =0; strcat(buffer,hmacHex); int l = strlen(buffer); assert( l+1 < STUN_MAX_STRING ); assert( l%4 == 0 ); username->sizeValue = l; memcpy(username->value,buffer,l); username->value[l]=0; //if (verbose) clog << "computed username=" << username.value << endl; } void stunCreatePassword(const StunAtrString& username, StunAtrString* password) { char hmac[20]; char key[] = "Fluffy"; //char buffer[STUN_MAX_STRING]; computeHmac(hmac, username.value, strlen(username.value), key, strlen(key)); toHex(hmac, 20, password->value); password->sizeValue = 40; password->value[40]=0; //clog << "password=" << password->value << endl; } UInt64 stunGetSystemTimeSecs() { UInt64 time=0; #if defined(WIN32) SYSTEMTIME t; // CJ TODO - this probably has bug on wrap around every 24 hours GetSystemTime( &t ); time = (t.wHour*60+t.wMinute)*60+t.wSecond; #else struct timeval now; gettimeofday( &now , NULL ); //assert( now ); time = now.tv_sec; #endif return time; } ostream& operator<< ( ostream& strm, const UInt128& r ) { strm << int(r.octet[0]); for ( int i=1; i<16; i++ ) { strm << ':' << int(r.octet[i]); } return strm; } ostream& operator<<( ostream& strm, const StunAddress4& addr) { UInt32 ip = addr.addr; strm << ((int)(ip>>24)&0xFF) << "."; strm << ((int)(ip>>16)&0xFF) << "."; strm << ((int)(ip>> 8)&0xFF) << "."; strm << ((int)(ip>> 0)&0xFF) ; strm << ":" << addr.port; return strm; } // returns true if it scucceeded bool stunParseHostName( char* peerName, UInt32& ip, UInt16& portVal, UInt16 defaultPort ) { in_addr sin_addr; char host[512]; strncpy(host,peerName,512); host[512-1]='\0'; char* port = NULL; int portNum = defaultPort; // pull out the port part if present. char* sep = strchr(host,':'); if ( sep == NULL ) { portNum = defaultPort; } else { *sep = '\0'; port = sep + 1; // set port part char* endPtr=NULL; portNum = strtol(port,&endPtr,10); if ( endPtr != NULL ) { if ( *endPtr != '\0' ) { portNum = defaultPort; } } } if ( portNum < 1024 ) return false; if ( portNum >= 0xFFFF ) return false; // figure out the host part struct hostent* h; #ifdef WIN32 assert( strlen(host) >= 1 ); if ( isdigit( host[0] ) ) { // assume it is a ip address unsigned long a = inet_addr(host); //cerr << "a=0x" << hex << a << dec << endl; ip = ntohl( a ); } else { // assume it is a host name h = gethostbyname( host ); if ( h == NULL ) { int err = getErrno(); std::cerr << "error was " << err << std::endl; assert( err != WSANOTINITIALISED ); ip = ntohl( 0x7F000001L ); return false; } else { sin_addr = *(struct in_addr*)h->h_addr; ip = ntohl( sin_addr.s_addr ); } } #else h = gethostbyname( host ); if ( h == NULL ) { int err = getErrno(); std::cerr << "error was " << err << std::endl; ip = ntohl( 0x7F000001L ); return false; } else { sin_addr = *(struct in_addr*)h->h_addr; ip = ntohl( sin_addr.s_addr ); } #endif portVal = portNum; return true; } bool stunParseServerName( char* name, StunAddress4& addr) { assert(name); // TODO - put in DNS SRV stuff. bool ret = stunParseHostName( name, addr.addr, addr.port, 3478); if ( ret != true ) { addr.port=0xFFFF; } return ret; } static void stunCreateErrorResponse(StunMessage& response, int cl, int number, const char* msg) { response.msgHdr.msgType = BindErrorResponseMsg; response.hasErrorCode = true; response.errorCode.errorClass = cl; response.errorCode.number = number; strcpy(response.errorCode.reason, msg); } // Twinkle StunMessage *stunBuildError(const StunMessage &m, int code, const char *reason) { StunMessage *err = new StunMessage(); MEMMAN_NEW(err); stunCreateErrorResponse(*err, code / 100, code % 100, reason); for ( int i=0; i<16; i++ ) { err->msgHdr.id.octet[i] = m.msgHdr.id.octet[i]; } return err; } string stunMsg2Str(const StunMessage &m) { string s = "STUN "; // Message type if (m.msgHdr.msgType == BindRequestMsg) { s += "Bind Request"; } else if (m.msgHdr.msgType == BindResponseMsg) { s += "Bind Response"; } else if (m.msgHdr.msgType == BindErrorResponseMsg) { s += "Bind Error Response"; } else if (m.msgHdr.msgType == SharedSecretRequestMsg) { s += "Shared Secret Request"; } else if (m.msgHdr.msgType == SharedSecretResponseMsg) { s += "Shared Secret Response"; } else if (m.msgHdr.msgType == SharedSecretErrorResponseMsg) { s += "Shared Secret Error Response"; } s += "\n"; // Message ID s += "ID = "; for ( int i=0; i<16; i++ ) { char buf[3]; snprintf(buf, 3, "%X", m.msgHdr.id.octet[i]); s += buf; } s += "\n"; if (m.hasMappedAddress) { s += "Mapped Address = "; s += h_ip2str(m.mappedAddress.ipv4.addr); s += ':'; s += int2str(m.mappedAddress.ipv4.port); s += "\n"; } if (m.hasResponseAddress) { s += "Response Address = "; s += h_ip2str(m.responseAddress.ipv4.addr); s += ':'; s += int2str(m.responseAddress.ipv4.port); s += "\n"; } if (m.hasChangeRequest) { s += "Change Request = "; bool change_flags = false; if (m.changeRequest.value & ChangeIpFlag) { s += "change IP"; change_flags = true; } if (m.changeRequest.value & ChangePortFlag) { if (change_flags) s += ", "; s += "change port"; change_flags = true; } if (!change_flags) s += "none"; s += "\n"; } if (m.hasSourceAddress) { s += "Source Address = "; s += h_ip2str(m.sourceAddress.ipv4.addr); s += ':'; s += int2str(m.sourceAddress.ipv4.port); s += "\n"; } if (m.hasChangedAddress) { s += "Changed Address = "; s += h_ip2str(m.changedAddress.ipv4.addr); s += ':'; s += int2str(m.changedAddress.ipv4.port); s += "\n"; } if (m.hasErrorCode) { s += "Error Code = "; s += int2str(m.errorCode.errorClass * 100 + m.errorCode.number); s += ' '; s += m.errorCode.reason; s += "\n"; } return s; } bool stunEqualId(const StunMessage &m1, const StunMessage &m2) { for ( int i=0; i<16; i++ ) { if (m1.msgHdr.id.octet[i] != m2.msgHdr.id.octet[i]) { return false; } } return true; } string stunNatType2Str(NatType t) { switch (t) { case StunTypeUnknown: return "Unknow"; case StunTypeOpen: return "Open"; case StunTypeConeNat: return "Full cone"; case StunTypeRestrictedNat: return "Restriced cone"; case StunTypePortRestrictedNat: return "Port restricted cone"; case StunTypeSymNat: return "Symmetric"; case StunTypeSymFirewall: return "Symmetric firewall;"; case StunTypeBlocked: return "Blocked."; case StunTypeFailure: return "Failed"; default: return "NULL"; } } // Twinkle #if 0 static void stunCreateSharedSecretErrorResponse(StunMessage& response, int cl, int number, const char* msg) { response.msgHdr.msgType = SharedSecretErrorResponseMsg; response.hasErrorCode = true; response.errorCode.errorClass = cl; response.errorCode.number = number; strcpy(response.errorCode.reason, msg); } #endif static void stunCreateSharedSecretResponse(const StunMessage& request, const StunAddress4& source, StunMessage& response) { response.msgHdr.msgType = SharedSecretResponseMsg; response.msgHdr.id = request.msgHdr.id; response.hasUsername = true; stunCreateUserName( source, &response.username); response.hasPassword = true; stunCreatePassword( response.username, &response.password); } // This funtion takes a single message sent to a stun server, parses // and constructs an apropriate repsonse - returns true if message is // valid bool stunServerProcessMsg( char* buf, unsigned int bufLen, StunAddress4& from, StunAddress4& secondary, StunAddress4& myAddr, StunAddress4& altAddr, StunMessage* resp, StunAddress4* destination, StunAtrString* hmacPassword, bool* changePort, bool* changeIp, bool verbose) { // set up information for default response memset( resp, 0 , sizeof(*resp) ); *changeIp = false; *changePort = false; StunMessage req; bool ok = stunParseMessage( buf,bufLen, req, verbose); if (!ok) // Complete garbage, drop it on the floor { if (verbose) clog << "Request did not parse" << endl; return false; } if (verbose) clog << "Request parsed ok" << endl; StunAddress4 mapped = req.mappedAddress.ipv4; StunAddress4 respondTo = req.responseAddress.ipv4; UInt32 flags = req.changeRequest.value; switch (req.msgHdr.msgType) { case SharedSecretRequestMsg: if(verbose) clog << "Received SharedSecretRequestMsg on udp. send error 433." << endl; // !cj! - should fix so you know if this came over TLS or UDP stunCreateSharedSecretResponse(req, from, *resp); //stunCreateSharedSecretErrorResponse(*resp, 4, 33, "this request must be over TLS"); return true; case BindRequestMsg: if (!req.hasMessageIntegrity) { if (verbose) clog << "BindRequest does not contain MessageIntegrity" << endl; if (0) // !jf! mustAuthenticate { if(verbose) clog << "Received BindRequest with no MessageIntegrity. Sending 401." << endl; stunCreateErrorResponse(*resp, 4, 1, "Missing MessageIntegrity"); return true; } } else { if (!req.hasUsername) { if (verbose) clog << "No UserName. Send 432." << endl; stunCreateErrorResponse(*resp, 4, 32, "No UserName and contains MessageIntegrity"); return true; } else { if (verbose) clog << "Validating username: " << req.username.value << endl; // !jf! could retrieve associated password from provisioning here if (strcmp(req.username.value, "test") == 0) { if (0) { // !jf! if the credentials are stale stunCreateErrorResponse(*resp, 4, 30, "Stale credentials on BindRequest"); return true; } else { if (verbose) clog << "Validating MessageIntegrity" << endl; // need access to shared secret unsigned char hmac[20]; #ifndef NOSSL unsigned int hmacSize=20; HMAC(EVP_sha1(), "1234", 4, reinterpret_cast(buf), bufLen-20-4, hmac, &hmacSize); assert(hmacSize == 20); #endif if (memcmp(buf, hmac, 20) != 0) { if (verbose) clog << "MessageIntegrity is bad. Sending " << endl; stunCreateErrorResponse(*resp, 4, 3, "Unknown username. Try test with password 1234"); return true; } // need to compute this later after message is filled in resp->hasMessageIntegrity = true; assert(req.hasUsername); resp->hasUsername = true; resp->username = req.username; // copy username in } } else { if (verbose) clog << "Invalid username: " << req.username.value << "Send 430." << endl; } } } // TODO !jf! should check for unknown attributes here and send 420 listing the // unknown attributes. if ( respondTo.port == 0 ) respondTo = from; if ( mapped.port == 0 ) mapped = from; *changeIp = ( flags & ChangeIpFlag )?true:false; *changePort = ( flags & ChangePortFlag )?true:false; if (verbose) { clog << "Request is valid:" << endl; clog << "\t flags=" << flags << endl; clog << "\t changeIp=" << *changeIp << endl; clog << "\t changePort=" << *changePort << endl; clog << "\t from = " << from << endl; clog << "\t respond to = " << respondTo << endl; clog << "\t mapped = " << mapped << endl; } // form the outgoing message resp->msgHdr.msgType = BindResponseMsg; for ( int i=0; i<16; i++ ) { resp->msgHdr.id.octet[i] = req.msgHdr.id.octet[i]; } if ( req.xorOnly == false ) { resp->hasMappedAddress = true; resp->mappedAddress.ipv4.port = mapped.port; resp->mappedAddress.ipv4.addr = mapped.addr; } if (1) // do xorMapped address or not { resp->hasXorMappedAddress = true; UInt16 id16 = req.msgHdr.id.octet[7]<<8 | req.msgHdr.id.octet[6]; UInt32 id32 = req.msgHdr.id.octet[7]<<24 | req.msgHdr.id.octet[6]<<16 | req.msgHdr.id.octet[5]<<8 | req.msgHdr.id.octet[4];; resp->xorMappedAddress.ipv4.port = mapped.port^id16; resp->xorMappedAddress.ipv4.addr = mapped.addr^id32; } resp->hasSourceAddress = true; resp->sourceAddress.ipv4.port = (*changePort) ? altAddr.port : myAddr.port; resp->sourceAddress.ipv4.addr = (*changeIp) ? altAddr.addr : myAddr.addr; resp->hasChangedAddress = true; resp->changedAddress.ipv4.port = altAddr.port; resp->changedAddress.ipv4.addr = altAddr.addr; if ( secondary.port != 0 ) { resp->hasSecondaryAddress = true; resp->secondaryAddress.ipv4.port = secondary.port; resp->secondaryAddress.ipv4.addr = secondary.addr; } if ( req.hasUsername && req.username.sizeValue > 0 ) { // copy username in resp->hasUsername = true; assert( req.username.sizeValue % 4 == 0 ); assert( req.username.sizeValue < STUN_MAX_STRING ); memcpy( resp->username.value, req.username.value, req.username.sizeValue ); resp->username.sizeValue = req.username.sizeValue; } if (1) // add ServerName { resp->hasServerName = true; const char serverName[] = "Vovida.org " STUN_VERSION; // must pad to mult of 4 assert( sizeof(serverName) < STUN_MAX_STRING ); //cerr << "sizeof serverName is " << sizeof(serverName) << endl; assert( sizeof(serverName)%4 == 0 ); memcpy( resp->serverName.value, serverName, sizeof(serverName)); resp->serverName.sizeValue = sizeof(serverName); } if ( req.hasMessageIntegrity & req.hasUsername ) { // this creates the password that will be used in the HMAC when then // messages is sent stunCreatePassword( req.username, hmacPassword ); } if (req.hasUsername && (req.username.sizeValue > 64 ) ) { UInt32 source; assert( sizeof(int) == sizeof(UInt32) ); sscanf(req.username.value, "%x", &source); resp->hasReflectedFrom = true; resp->reflectedFrom.ipv4.port = 0; resp->reflectedFrom.ipv4.addr = source; } destination->port = respondTo.port; destination->addr = respondTo.addr; return true; default: if (verbose) clog << "Unknown or unsupported request " << endl; return false; } assert(0); return false; } bool stunInitServer(StunServerInfo& info, const StunAddress4& myAddr, const StunAddress4& altAddr, int startMediaPort, bool verbose ) { assert( myAddr.port != 0 ); assert( altAddr.port!= 0 ); assert( myAddr.addr != 0 ); //assert( altAddr.addr != 0 ); info.myAddr = myAddr; info.altAddr = altAddr; info.myFd = INVALID_SOCKET; info.altPortFd = INVALID_SOCKET; info.altIpFd = INVALID_SOCKET; info.altIpPortFd = INVALID_SOCKET; memset(info.relays, 0, sizeof(info.relays)); if (startMediaPort > 0) { info.relay = true; for (int i=0; irelayPort = startMediaPort+i; relay->fd = 0; relay->expireTime = 0; } } else { info.relay = false; } if ((info.myFd = openPort(myAddr.port, myAddr.addr,verbose)) == INVALID_SOCKET) { clog << "Can't open " << myAddr << endl; stunStopServer(info); return false; } //if (verbose) clog << "Opened " << myAddr.addr << ":" << myAddr.port << " --> " << info.myFd << endl; if ((info.altPortFd = openPort(altAddr.port,myAddr.addr,verbose)) == INVALID_SOCKET) { clog << "Can't open " << myAddr << endl; stunStopServer(info); return false; } //if (verbose) clog << "Opened " << myAddr.addr << ":" << altAddr.port << " --> " << info.altPortFd << endl; info.altIpFd = INVALID_SOCKET; if ( altAddr.addr != 0 ) { if ((info.altIpFd = openPort( myAddr.port, altAddr.addr,verbose)) == INVALID_SOCKET) { clog << "Can't open " << altAddr << endl; stunStopServer(info); return false; } //if (verbose) clog << "Opened " << altAddr.addr << ":" << myAddr.port << " --> " << info.altIpFd << endl;; } info.altIpPortFd = INVALID_SOCKET; if ( altAddr.addr != 0 ) { if ((info.altIpPortFd = openPort(altAddr.port, altAddr.addr,verbose)) == INVALID_SOCKET) { clog << "Can't open " << altAddr << endl; stunStopServer(info); return false; } //if (verbose) clog << "Opened " << altAddr.addr << ":" << altAddr.port << " --> " << info.altIpPortFd << endl;; } return true; } void stunStopServer(StunServerInfo& info) { if (info.myFd > 0) closesocket(info.myFd); if (info.altPortFd > 0) closesocket(info.altPortFd); if (info.altIpFd > 0) closesocket(info.altIpFd); if (info.altIpPortFd > 0) closesocket(info.altIpPortFd); if (info.relay) { for (int i=0; ifd) { closesocket(relay->fd); relay->fd = 0; } } } } bool stunServerProcess(StunServerInfo& info, bool verbose) { char msg[STUN_MAX_MESSAGE_SIZE]; int msgLen = sizeof(msg); bool ok = false; bool recvAltIp =false; bool recvAltPort = false; fd_set fdSet; #ifdef WIN32 unsigned int maxFd=0; #else int maxFd=0; #endif FD_ZERO(&fdSet); FD_SET(info.myFd,&fdSet); if ( info.myFd >= maxFd ) maxFd=info.myFd+1; FD_SET(info.altPortFd,&fdSet); if ( info.altPortFd >= maxFd ) maxFd=info.altPortFd+1; if ( info.altIpFd != INVALID_SOCKET ) { FD_SET(info.altIpFd,&fdSet); if (info.altIpFd>=maxFd) maxFd=info.altIpFd+1; } if ( info.altIpPortFd != INVALID_SOCKET ) { FD_SET(info.altIpPortFd,&fdSet); if (info.altIpPortFd>=maxFd) maxFd=info.altIpPortFd+1; } if (info.relay) { for (int i=0; ifd) { FD_SET(relay->fd, &fdSet); if (relay->fd >= maxFd) maxFd=relay->fd+1; } } } if ( info.altIpFd != INVALID_SOCKET ) { FD_SET(info.altIpFd,&fdSet); if (info.altIpFd>=maxFd) maxFd=info.altIpFd+1; } if ( info.altIpPortFd != INVALID_SOCKET ) { FD_SET(info.altIpPortFd,&fdSet); if (info.altIpPortFd>=maxFd) maxFd=info.altIpPortFd+1; } struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 1000; int e = select( maxFd, &fdSet, NULL,NULL, &tv ); if (e < 0) { int err = getErrno(); clog << "Error on select: " << strerror(err) << endl; } else if (e >= 0) { StunAddress4 from; // do the media relaying if (info.relay) { time_t now = time(0); for (int i=0; ifd) { if (FD_ISSET(relay->fd, &fdSet)) { char msg[MAX_RTP_MSG_SIZE]; int msgLen = sizeof(msg); StunAddress4 rtpFrom; ok = getMessage( relay->fd, msg, &msgLen, &rtpFrom.addr, &rtpFrom.port ,verbose); if (ok) { sendMessage(info.myFd, msg, msgLen, relay->destination.addr, relay->destination.port, verbose); relay->expireTime = now + MEDIA_RELAY_TIMEOUT; if ( verbose ) clog << "Relay packet on " << relay->fd << " from " << rtpFrom << " -> " << relay->destination << endl; } } else if (now > relay->expireTime) { closesocket(relay->fd); relay->fd = 0; } } } } if (FD_ISSET(info.myFd,&fdSet)) { if (verbose) clog << "received on A1:P1" << endl; recvAltIp = false; recvAltPort = false; ok = getMessage( info.myFd, msg, &msgLen, &from.addr, &from.port,verbose ); } else if (FD_ISSET(info.altPortFd, &fdSet)) { if (verbose) clog << "received on A1:P2" << endl; recvAltIp = false; recvAltPort = true; ok = getMessage( info.altPortFd, msg, &msgLen, &from.addr, &from.port,verbose ); } else if ( (info.altIpFd!=INVALID_SOCKET) && FD_ISSET(info.altIpFd,&fdSet)) { if (verbose) clog << "received on A2:P1" << endl; recvAltIp = true; recvAltPort = false; ok = getMessage( info.altIpFd, msg, &msgLen, &from.addr, &from.port ,verbose); } else if ( (info.altIpPortFd!=INVALID_SOCKET) && FD_ISSET(info.altIpPortFd, &fdSet)) { if (verbose) clog << "received on A2:P2" << endl; recvAltIp = true; recvAltPort = true; ok = getMessage( info.altIpPortFd, msg, &msgLen, &from.addr, &from.port,verbose ); } else { return true; } int relayPort = 0; if (info.relay) { for (int i=0; idestination.addr == from.addr && relay->destination.port == from.port) { relayPort = relay->relayPort; relay->expireTime = time(0) + MEDIA_RELAY_TIMEOUT; break; } } if (relayPort == 0) { for (int i=0; ifd == 0) { if ( verbose ) clog << "Open relay port " << relay->relayPort << endl; relay->fd = openPort(relay->relayPort, info.myAddr.addr, verbose); relay->destination.addr = from.addr; relay->destination.port = from.port; relay->expireTime = time(0) + MEDIA_RELAY_TIMEOUT; relayPort = relay->relayPort; break; } } } } if ( !ok ) { if ( verbose ) clog << "Get message did not return a valid message" < 0) && ( count < maxRet) ) { struct ifreq* ifr = (struct ifreq *)ptr; int si = sizeof(ifr->ifr_name) + sizeof(struct sockaddr); tl -= si; ptr += si; //char* name = ifr->ifr_ifrn.ifrn_name; //cerr << "name = " << name << endl; struct ifreq ifr2; ifr2 = *ifr; e = ioctl(s,SIOCGIFADDR,&ifr2); if ( e == -1 ) { break; } //cerr << "ioctl addr e = " << e << endl; struct sockaddr a = ifr2.ifr_addr; struct sockaddr_in* addr = (struct sockaddr_in*) &a; UInt32 ai = ntohl( addr->sin_addr.s_addr ); if (int((ai>>24)&0xFF) != 127) { addresses[count++] = ai; } #if 0 cerr << "Detected interface " << int((ai>>24)&0xFF) << "." << int((ai>>16)&0xFF) << "." << int((ai>> 8)&0xFF) << "." << int((ai )&0xFF) << endl; #endif } closesocket(s); return count; #endif } void stunBuildReqSimple( StunMessage* msg, const StunAtrString& username, bool changePort, bool changeIp, unsigned int id ) { assert( msg ); memset( msg , 0 , sizeof(*msg) ); msg->msgHdr.msgType = BindRequestMsg; for ( int i=0; i<16; i=i+4 ) { assert(i+3<16); int r = stunRand(); msg->msgHdr.id.octet[i+0]= r>>0; msg->msgHdr.id.octet[i+1]= r>>8; msg->msgHdr.id.octet[i+2]= r>>16; msg->msgHdr.id.octet[i+3]= r>>24; } if ( id != 0 ) { msg->msgHdr.id.octet[0] = id; } msg->hasChangeRequest = true; msg->changeRequest.value =(changeIp?ChangeIpFlag:0) | (changePort?ChangePortFlag:0); if ( username.sizeValue > 0 ) { msg->hasUsername = true; msg->username = username; } } static void stunSendTest( StunSocket myFd, StunAddress4& dest, const StunAtrString& username, const StunAtrString& password, int testNum, bool verbose ) { assert( dest.addr != 0 ); assert( dest.port != 0 ); bool changePort=false; bool changeIP=false; bool discard=false; switch (testNum) { case 1: case 10: case 11: break; case 2: //changePort=true; changeIP=true; break; case 3: changePort=true; break; case 4: changeIP=true; break; case 5: discard=true; break; default: cerr << "Test " << testNum <<" is unknown\n"; assert(0); } StunMessage req; memset(&req, 0, sizeof(StunMessage)); stunBuildReqSimple( &req, username, changePort , changeIP , testNum ); char buf[STUN_MAX_MESSAGE_SIZE]; int len = STUN_MAX_MESSAGE_SIZE; len = stunEncodeMessage( req, buf, len, password,verbose ); if ( verbose ) { clog << "About to send msg of len " << len << " to " << dest << endl; } sendMessage( myFd, buf, len, dest.addr, dest.port, verbose ); // add some delay so the packets don't get sent too quickly #ifdef WIN32 // !cj! TODO - should fix this up in windows clock_t now = clock(); assert( CLOCKS_PER_SEC == 1000 ); while ( clock() <= now+10 ) { }; #else usleep(10*1000); #endif } void stunGetUserNameAndPassword( const StunAddress4& dest, StunAtrString* username, StunAtrString* password) { // !cj! This is totally bogus - need to make TLS connection to dest and get a // username and password to use stunCreateUserName(dest, username); stunCreatePassword(*username, password); } void stunTest( StunAddress4& dest, int testNum, bool verbose, StunAddress4* sAddr ) { assert( dest.addr != 0 ); assert( dest.port != 0 ); int port = randomPort(); UInt32 interfaceIp=0; if (sAddr) { interfaceIp = sAddr->addr; if ( sAddr->port != 0 ) { port = sAddr->port; } } StunSocket myFd = openPort(port,interfaceIp,verbose); StunAtrString username; StunAtrString password; username.sizeValue = 0; password.sizeValue = 0; #ifdef USE_TLS stunGetUserNameAndPassword( dest, username, password ); #endif stunSendTest( myFd, dest, username, password, testNum, verbose ); char msg[STUN_MAX_MESSAGE_SIZE]; int msgLen = STUN_MAX_MESSAGE_SIZE; StunAddress4 from; getMessage( myFd, msg, &msgLen, &from.addr, &from.port,verbose ); StunMessage resp; memset(&resp, 0, sizeof(StunMessage)); if ( verbose ) clog << "Got a response" << endl; bool ok = stunParseMessage( msg,msgLen, resp,verbose ); if ( verbose ) { clog << "\t ok=" << ok << endl; clog << "\t id=" << resp.msgHdr.id << endl; clog << "\t mappedAddr=" << resp.mappedAddress.ipv4 << endl; clog << "\t changedAddr=" << resp.changedAddress.ipv4 << endl; clog << endl; } if (sAddr) { sAddr->port = resp.mappedAddress.ipv4.port; sAddr->addr = resp.mappedAddress.ipv4.addr; } } NatType stunNatType( StunAddress4& dest, bool verbose, bool* preservePort, // if set, is return for if NAT preservers ports or not bool* hairpin, // if set, is the return for if NAT will hairpin packets int port, // port to use for the test, 0 to choose random port StunAddress4* sAddr // NIC to use ) { assert( dest.addr != 0 ); assert( dest.port != 0 ); if ( hairpin ) { *hairpin = false; } if ( port == 0 ) { port = randomPort(); } UInt32 interfaceIp=0; if (sAddr) { interfaceIp = sAddr->addr; } StunSocket myFd1 = openPort(port,interfaceIp,verbose); StunSocket myFd2 = openPort(port+1,interfaceIp,verbose); if ( ( myFd1 == INVALID_SOCKET) || ( myFd2 == INVALID_SOCKET) ) { cerr << "Some problem opening port/interface to send on" << endl; return StunTypeFailure; } assert( myFd1 != INVALID_SOCKET ); assert( myFd2 != INVALID_SOCKET ); bool respTestI=false; bool isNat=true; StunAddress4 testIchangedAddr; StunAddress4 testImappedAddr; bool respTestI2=false; bool mappedIpSame = true; StunAddress4 testI2mappedAddr; StunAddress4 testI2dest=dest; bool respTestII=false; bool respTestIII=false; bool respTestHairpin=false; memset(&testImappedAddr,0,sizeof(testImappedAddr)); StunAtrString username; StunAtrString password; username.sizeValue = 0; password.sizeValue = 0; #ifdef USE_TLS stunGetUserNameAndPassword( dest, username, password ); #endif //stunSendTest( myFd1, dest, username, password, 1, verbose ); int count=0; while ( count < 7 ) { struct timeval tv; fd_set fdSet; #ifdef WIN32 unsigned int fdSetSize; #else int fdSetSize; #endif FD_ZERO(&fdSet); fdSetSize=0; FD_SET(myFd1,&fdSet); fdSetSize = (myFd1+1>fdSetSize) ? myFd1+1 : fdSetSize; FD_SET(myFd2,&fdSet); fdSetSize = (myFd2+1>fdSetSize) ? myFd2+1 : fdSetSize; tv.tv_sec=0; tv.tv_usec=150*1000; // 150 ms if ( count == 0 ) tv.tv_usec=0; int err = select(fdSetSize, &fdSet, NULL, NULL, &tv); int e = getErrno(); if ( err == SOCKET_ERROR ) { // error occured cerr << "Error " << e << " " << strerror(e) << " in select" << endl; closesocket(myFd1); closesocket(myFd2); return StunTypeFailure; } else if ( err == 0 ) { // timeout occured count++; if ( !respTestI ) { stunSendTest( myFd1, dest, username, password, 1 ,verbose ); } if ( (!respTestI2) && respTestI ) { // check the address to send to if valid if ( ( testI2dest.addr != 0 ) && ( testI2dest.port != 0 ) ) { stunSendTest( myFd1, testI2dest, username, password, 10 ,verbose); } } if ( !respTestII ) { stunSendTest( myFd2, dest, username, password, 2 ,verbose ); } if ( !respTestIII ) { stunSendTest( myFd2, dest, username, password, 3 ,verbose ); } if ( respTestI && (!respTestHairpin) ) { if ( ( testImappedAddr.addr != 0 ) && ( testImappedAddr.port != 0 ) ) { stunSendTest( myFd1, testImappedAddr, username, password, 11 ,verbose ); } } } else { //if (verbose) clog << "-----------------------------------------" << endl; assert( err>0 ); // data is avialbe on some fd for ( int i=0; i<2; i++) { StunSocket myFd; if ( i==0 ) { myFd=myFd1; } else { myFd=myFd2; } if ( myFd!=INVALID_SOCKET ) { if ( FD_ISSET(myFd,&fdSet) ) { char msg[STUN_MAX_MESSAGE_SIZE]; int msgLen = sizeof(msg); StunAddress4 from; getMessage( myFd, msg, &msgLen, &from.addr, &from.port,verbose ); StunMessage resp; memset(&resp, 0, sizeof(StunMessage)); stunParseMessage( msg,msgLen, resp,verbose ); if ( verbose ) { clog << "Received message of type " << resp.msgHdr.msgType << " id=" << (int)(resp.msgHdr.id.octet[0]) << endl; } switch( resp.msgHdr.id.octet[0] ) { case 1: { if ( !respTestI ) { testIchangedAddr.addr = resp.changedAddress.ipv4.addr; testIchangedAddr.port = resp.changedAddress.ipv4.port; testImappedAddr.addr = resp.mappedAddress.ipv4.addr; testImappedAddr.port = resp.mappedAddress.ipv4.port; if ( preservePort ) { *preservePort = ( testImappedAddr.port == port ); } testI2dest.addr = resp.changedAddress.ipv4.addr; if (sAddr) { sAddr->port = testImappedAddr.port; sAddr->addr = testImappedAddr.addr; } count = 0; } respTestI=true; } break; case 2: { respTestII=true; } break; case 3: { respTestIII=true; } break; case 10: { if ( !respTestI2 ) { testI2mappedAddr.addr = resp.mappedAddress.ipv4.addr; testI2mappedAddr.port = resp.mappedAddress.ipv4.port; mappedIpSame = false; if ( (testI2mappedAddr.addr == testImappedAddr.addr ) && (testI2mappedAddr.port == testImappedAddr.port )) { mappedIpSame = true; } } respTestI2=true; } break; case 11: { if ( hairpin ) { *hairpin = true; } respTestHairpin = true; } break; } } } } } } // see if we can bind to this address //cerr << "try binding to " << testImappedAddr << endl; StunSocket s = openPort( 0/*use ephemeral*/, testImappedAddr.addr, false ); if ( s != INVALID_SOCKET ) { closesocket(s); isNat = false; //cerr << "binding worked" << endl; } else { isNat = true; //cerr << "binding failed" << endl; } closesocket(myFd1); closesocket(myFd2); if (verbose) { clog << "test I = " << respTestI << endl; clog << "test II = " << respTestII << endl; clog << "test III = " << respTestIII << endl; clog << "test I(2) = " << respTestI2 << endl; clog << "is nat = " << isNat <. * */ // Local Variables: // mode:c++ // c-file-style:"ellemtel" // c-file-offsets:((case-label . +)) // indent-tabs-mode:nil // End: twinkle-1.10.1/src/stun/stun.h000066400000000000000000000253251277565361200162160ustar00rootroot00000000000000#ifndef STUN_H #define STUN_H #include #include #include using namespace std; // if you change this version, change in makefile too #define STUN_VERSION "0.94" #define STUN_MAX_STRING 256 #define STUN_MAX_UNKNOWN_ATTRIBUTES 8 #define STUN_MAX_MESSAGE_SIZE 2048 #define STUN_PORT 3478 // define some basic types typedef unsigned char UInt8; typedef unsigned short UInt16; typedef unsigned int UInt32; #if defined( WIN32 ) typedef unsigned __int64 UInt64; #else typedef unsigned long long UInt64; #endif typedef struct { unsigned char octet[16]; } UInt128; /// define a structure to hold a stun address const UInt8 IPv4Family = 0x01; const UInt8 IPv6Family = 0x02; // define flags const UInt32 ChangeIpFlag = 0x04; const UInt32 ChangePortFlag = 0x02; // define stun attribute const UInt16 MappedAddress = 0x0001; const UInt16 ResponseAddress = 0x0002; const UInt16 ChangeRequest = 0x0003; const UInt16 SourceAddress = 0x0004; const UInt16 ChangedAddress = 0x0005; const UInt16 Username = 0x0006; const UInt16 Password = 0x0007; const UInt16 MessageIntegrity = 0x0008; const UInt16 ErrorCode = 0x0009; const UInt16 UnknownAttribute = 0x000A; const UInt16 ReflectedFrom = 0x000B; const UInt16 XorMappedAddress = 0x0020; const UInt16 XorOnly = 0x0021; const UInt16 ServerName = 0x0022; const UInt16 SecondaryAddress = 0x0050; // Non standard extention // define types for a stun message const UInt16 BindRequestMsg = 0x0001; const UInt16 BindResponseMsg = 0x0101; const UInt16 BindErrorResponseMsg = 0x0111; const UInt16 SharedSecretRequestMsg = 0x0002; const UInt16 SharedSecretResponseMsg = 0x0102; const UInt16 SharedSecretErrorResponseMsg = 0x0112; typedef struct { UInt16 msgType; UInt16 msgLength; UInt128 id; } StunMsgHdr; typedef struct { UInt16 type; UInt16 length; } StunAtrHdr; typedef struct { UInt16 port; UInt32 addr; } StunAddress4; typedef struct { UInt8 pad; UInt8 family; StunAddress4 ipv4; } StunAtrAddress4; typedef struct { UInt32 value; } StunAtrChangeRequest; typedef struct { UInt16 pad; // all 0 UInt8 errorClass; UInt8 number; char reason[STUN_MAX_STRING]; UInt16 sizeReason; } StunAtrError; typedef struct { UInt16 attrType[STUN_MAX_UNKNOWN_ATTRIBUTES]; UInt16 numAttributes; } StunAtrUnknown; typedef struct { char value[STUN_MAX_STRING]; UInt16 sizeValue; } StunAtrString; typedef struct { char hash[20]; } StunAtrIntegrity; typedef enum { HmacUnkown=0, HmacOK, HmacBadUserName, HmacUnkownUserName, HmacFailed, } StunHmacStatus; typedef struct { StunMsgHdr msgHdr; bool hasMappedAddress; StunAtrAddress4 mappedAddress; bool hasResponseAddress; StunAtrAddress4 responseAddress; bool hasChangeRequest; StunAtrChangeRequest changeRequest; bool hasSourceAddress; StunAtrAddress4 sourceAddress; bool hasChangedAddress; StunAtrAddress4 changedAddress; bool hasUsername; StunAtrString username; bool hasPassword; StunAtrString password; bool hasMessageIntegrity; StunAtrIntegrity messageIntegrity; bool hasErrorCode; StunAtrError errorCode; bool hasUnknownAttributes; StunAtrUnknown unknownAttributes; bool hasReflectedFrom; StunAtrAddress4 reflectedFrom; bool hasXorMappedAddress; StunAtrAddress4 xorMappedAddress; bool xorOnly; bool hasServerName; StunAtrString serverName; bool hasSecondaryAddress; StunAtrAddress4 secondaryAddress; } StunMessage; // Define enum with different types of NAT typedef enum { StunTypeUnknown=0, StunTypeOpen, StunTypeConeNat, StunTypeRestrictedNat, StunTypePortRestrictedNat, StunTypeSymNat, StunTypeSymFirewall, StunTypeBlocked, StunTypeFailure } NatType; #ifdef WIN32 typedef SOCKET StunSocket; #else typedef int StunSocket; #endif #define MAX_MEDIA_RELAYS 500 #define MAX_RTP_MSG_SIZE 1500 #define MEDIA_RELAY_TIMEOUT 3*60 typedef struct { int relayPort; // media relay port int fd; // media relay file descriptor StunAddress4 destination; // NAT IP:port time_t expireTime; // if no activity after time, close the socket } StunMediaRelay; typedef struct { StunAddress4 myAddr; StunAddress4 altAddr; StunSocket myFd; StunSocket altPortFd; StunSocket altIpFd; StunSocket altIpPortFd; bool relay; // true if media relaying is to be done StunMediaRelay relays[MAX_MEDIA_RELAYS]; } StunServerInfo; bool stunParseMessage( char* buf, unsigned int bufLen, StunMessage& message, bool verbose ); void stunBuildReqSimple( StunMessage* msg, const StunAtrString& username, bool changePort, bool changeIp, unsigned int id=0 ); unsigned int stunEncodeMessage( const StunMessage& message, char* buf, unsigned int bufLen, const StunAtrString& password, bool verbose); void stunCreateUserName(const StunAddress4& addr, StunAtrString* username); void stunGetUserNameAndPassword( const StunAddress4& dest, StunAtrString* username, StunAtrString* password); void stunCreatePassword(const StunAtrString& username, StunAtrString* password); // Twinkle // Build an error response StunMessage *stunBuildError(const StunMessage &m, int code, const char *reason); // Create a string representation of a STUN message string stunMsg2Str(const StunMessage &m); bool stunEqualId(const StunMessage &m1, const StunMessage &m2); string stunNatType2Str(NatType t); // Twinkle int stunRand(); UInt64 stunGetSystemTimeSecs(); /// find the IP address of a the specified stun server - return false is fails parse bool stunParseServerName( char* serverName, StunAddress4& stunServerAddr); bool stunParseHostName( char* peerName, UInt32& ip, UInt16& portVal, UInt16 defaultPort ); /// return true if all is OK /// Create a media relay and do the STERN thing if startMediaPort is non-zero bool stunInitServer(StunServerInfo& info, const StunAddress4& myAddr, const StunAddress4& altAddr, int startMediaPort, bool verbose); void stunStopServer(StunServerInfo& info); /// return true if all is OK bool stunServerProcess(StunServerInfo& info, bool verbose); /// returns number of address found - take array or addres int stunFindLocalInterfaces(UInt32* addresses, int maxSize ); void stunTest( StunAddress4& dest, int testNum, bool verbose, StunAddress4* srcAddr=0 ); NatType stunNatType( StunAddress4& dest, bool verbose, bool* preservePort=0, // if set, is return for if NAT preservers ports or not bool* hairpin=0 , // if set, is the return for if NAT will hairpin packets int port=0, // port to use for the test, 0 to choose random port StunAddress4* sAddr=0 // NIC to use ); /// prints a StunAddress std::ostream& operator<<( std::ostream& strm, const StunAddress4& addr); std::ostream& operator<< ( std::ostream& strm, const UInt128& ); bool stunServerProcessMsg( char* buf, unsigned int bufLen, StunAddress4& from, StunAddress4& myAddr, StunAddress4& altAddr, StunMessage* resp, StunAddress4* destination, StunAtrString* hmacPassword, bool* changePort, bool* changeIp, bool verbose); int stunOpenStunSocket( StunAddress4& dest, StunAddress4* mappedAddr, int port=0, StunAddress4* srcAddr=0, bool verbose=false ); bool stunOpenStunSocketPair( StunAddress4& dest, StunAddress4* mappedAddr, int* fd1, int* fd2, int srcPort=0, StunAddress4* srcAddr=0, bool verbose=false); #endif /* ==================================================================== * The Vovida Software License, Version 1.0 * * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The names "VOCAL", "Vovida Open Communication Application Library", * and "Vovida Open Communication Application Library (VOCAL)" must * not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact vocal@vovida.org. * * 4. Products derived from this software may not be called "VOCAL", nor * may "VOCAL" appear in their name, without prior written * permission of Vovida Networks, Inc. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * ==================================================================== * * This software consists of voluntary contributions made by Vovida * Networks, Inc. and many individuals on behalf of Vovida Networks, * Inc. For more information on Vovida Networks, Inc., please see * . * */ // Local Variables: // mode:c++ // c-file-style:"ellemtel" // c-file-offsets:((case-label . +)) // indent-tabs-mode:nil // End: twinkle-1.10.1/src/stun/stun_transaction.cpp000066400000000000000000000467631277565361200211670ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include "stun_transaction.h" #include "events.h" #include "log.h" #include "phone.h" #include "sys_settings.h" #include "transaction_mgr.h" #include "translator.h" #include "util.h" #include "audits/memman.h" extern t_transaction_mgr *transaction_mgr; extern t_event_queue *evq_trans_layer; extern t_event_queue *evq_trans_mgr; extern t_event_queue *evq_sender; extern t_phone *phone; bool get_stun_binding(t_user *user_config, unsigned short src_port, unsigned long &mapped_ip, unsigned short &mapped_port, int &err_code, string &err_reason) { list destinations = user_config->get_stun_server().get_h_ip_srv("udp"); if (destinations.empty()) { // Cannot resolve STUN server address. log_file->write_header("::get_stun_binding", LOG_NORMAL, LOG_CRITICAL); log_file->write_raw("Failed to resolve: "); log_file->write_raw(user_config->get_stun_server().encode()); log_file->write_endl(); log_file->write_raw("Return internal STUN bind error: 404 Not Found"); log_file->write_endl(); log_file->write_footer(); err_code = 404; err_reason = "Not Found"; return false; } int num_transmissions = 0; int wait_intval = DUR_STUN_START_INTVAL; t_socket_udp sock(src_port); sock.connect(destinations.front().ipaddr, destinations.front().port); // Build STUN request char buf[STUN_MAX_MESSAGE_SIZE + 1]; StunMessage req_bind; StunAtrString stun_null_str; stun_null_str.sizeValue = 0; stunBuildReqSimple(&req_bind, stun_null_str, false, false); char req_msg[STUN_MAX_MESSAGE_SIZE]; int req_msg_size = stunEncodeMessage(req_bind, req_msg, STUN_MAX_MESSAGE_SIZE, stun_null_str, false); // Send STUN request and retransmit till a response is received. while (num_transmissions < STUN_MAX_TRANSMISSIONS) { bool ret; try { sock.send(req_msg, req_msg_size); } catch (int err) { // Socket error (probably ICMP error) // Failover to next destination log_file->write_report("Send failed. Failover to next destination.", "::get_stun_binding"); destinations.pop_front(); if (destinations.empty()) { log_file->write_report("No next destination for failover.", "::get_stun_binding"); break; } num_transmissions = 0; wait_intval = DUR_STUN_START_INTVAL; sock.connect(destinations.front().ipaddr, destinations.front().port); continue; } log_file->write_header("::get_stun_binding", LOG_STUN); log_file->write_raw("Send to: "); log_file->write_raw(h_ip2str(destinations.front().ipaddr)); log_file->write_raw(":"); log_file->write_raw(destinations.front().port); log_file->write_endl(); log_file->write_raw(stunMsg2Str(req_bind)); log_file->write_footer(); try { ret = sock.select_read(wait_intval); } catch (int err) { // Socket error (probably ICMP error) // Failover to next destination log_file->write_report("Select failed. Failover to next destination.", "::get_stun_binding"); destinations.pop_front(); if (destinations.empty()) { log_file->write_report("No next destination for failover.", "::get_stun_binding"); break; } num_transmissions = 0; wait_intval = DUR_STUN_START_INTVAL; sock.connect(destinations.front().ipaddr, destinations.front().port); continue; } if (!ret) { // Time out num_transmissions++; if (wait_intval < DUR_STUN_MAX_INTVAL) { wait_intval *= 2; } continue; } // A message has been received int resp_msg_size; try { resp_msg_size = sock.recv(buf, STUN_MAX_MESSAGE_SIZE + 1); } catch (int err) { // Socket error (probably ICMP error) // Failover to next destination log_file->write_report("Recv failed. Failover to next destination.", "::get_stun_binding"); destinations.pop_front(); if (destinations.empty()) { log_file->write_report("No next destination for failover.", "::get_stun_binding"); break; } num_transmissions = 0; wait_intval = DUR_STUN_START_INTVAL; sock.connect(destinations.front().ipaddr, destinations.front().port); continue; } StunMessage resp_bind; if (!stunParseMessage(buf, resp_msg_size, resp_bind, false)) { log_file->write_report( "Received faulty STUN message", "::get_stun_binding", LOG_STUN); num_transmissions++; if (wait_intval < DUR_STUN_MAX_INTVAL) { wait_intval *= 2; } continue; } log_file->write_header("::get_stun_binding", LOG_STUN); log_file->write_raw("Received from: "); log_file->write_raw(h_ip2str(destinations.front().ipaddr)); log_file->write_raw(":"); log_file->write_raw(destinations.front().port); log_file->write_endl(); log_file->write_raw(stunMsg2Str(resp_bind)); log_file->write_footer(); // Check if id in msgHdr matches if (!stunEqualId(resp_bind, req_bind)) { num_transmissions++; if (wait_intval < DUR_STUN_MAX_INTVAL) { wait_intval *= 2; } continue; } if (resp_bind.msgHdr.msgType == BindResponseMsg && resp_bind.hasMappedAddress) { // Bind response received mapped_ip = resp_bind.mappedAddress.ipv4.addr; mapped_port = resp_bind.mappedAddress.ipv4.port; return true; } if (resp_bind.msgHdr.msgType == BindErrorResponseMsg && resp_bind.hasErrorCode) { // Bind error received err_code = resp_bind.errorCode.errorClass * 100 + resp_bind.errorCode.number; char s[STUN_MAX_STRING + 1]; strncpy(s, resp_bind.errorCode.reason, STUN_MAX_STRING); s[STUN_MAX_STRING] = 0; err_reason = s; return false; } // A wrong response has been received. log_file->write_report( "Invalid STUN response received", "::get_stun_binding", LOG_NORMAL); err_code = 500; err_reason = "Server Error"; return false; } // Request timed out log_file->write_report("STUN request timeout", "::get_stun_binding", LOG_NORMAL); err_code = 408; err_reason = "Request Timeout"; return false; } bool stun_discover_nat(t_phone_user *pu, string &err_msg) { t_user *user_config = pu->get_user_profile(); // By default enable STUN. If for some reason we cannot perform // NAT discovery, then enable STUN. It will not harm, but only // create non-needed STUN transactions if we are not behind a NAT. pu->use_stun = true; pu->use_nat_keepalive = true; list destinations = user_config->get_stun_server().get_h_ip_srv("udp"); if (destinations.empty()) { // Cannot resolve STUN server address. log_file->write_header("::main", LOG_NORMAL, LOG_CRITICAL); log_file->write_raw("Failed to resolve: "); log_file->write_raw(user_config->get_stun_server().encode()); log_file->write_endl(); log_file->write_footer(); err_msg = TRANSLATE("Cannot resolve STUN server: %1"); err_msg = replace_first(err_msg, "%1", user_config->get_stun_server().encode()); return false; } while (!destinations.empty()) { StunAddress4 stun_ip4; stun_ip4.addr = destinations.front().ipaddr; stun_ip4.port = destinations.front().port; NatType nat_type = stunNatType(stun_ip4, false); log_file->write_header("::main"); log_file->write_raw("STUN NAT type discovery for "); log_file->write_raw(user_config->get_profile_name()); log_file->write_endl(); log_file->write_raw("NAT type: "); log_file->write_raw(stunNatType2Str(nat_type)); log_file->write_endl(); log_file->write_footer(); switch (nat_type) { case StunTypeOpen: // STUN is not needed. pu->use_stun = false; pu->use_nat_keepalive = false; return true; case StunTypeSymNat: err_msg = TRANSLATE("You are behind a symmetric NAT.\nSTUN will not work.\nConfigure a public IP address in the user profile\nand create the following static bindings (UDP) in your NAT."); err_msg += "\n\n"; err_msg += TRANSLATE("public IP: %1 --> private IP: %2 (SIP signaling)"); err_msg = replace_first(err_msg, "%1", int2str(sys_config->get_sip_port())); err_msg = replace_first(err_msg, "%2", int2str(sys_config->get_sip_port())); err_msg += "\n"; err_msg += TRANSLATE("public IP: %1-%2 --> private IP: %3-%4 (RTP/RTCP)"); err_msg = replace_first(err_msg, "%1", int2str(sys_config->get_rtp_port())); err_msg = replace_first(err_msg, "%2", int2str(sys_config->get_rtp_port() + 5)); err_msg = replace_first(err_msg, "%3", int2str(sys_config->get_rtp_port())); err_msg = replace_first(err_msg, "%4", int2str(sys_config->get_rtp_port() + 5)); pu->use_stun = false; pu->use_nat_keepalive = false; return false; case StunTypeSymFirewall: // STUN is not needed as we are on a pubic IP. // NAT keep alive is needed however to keep the firewall open. pu->use_stun = false; return true; case StunTypeBlocked: destinations.pop_front(); // The code for NAT type discovery does not handle // ICMP errors. So if the conclusion is that the network // connection is blocked, it might be due to a down STUN // server. Try alternative destination if avaliable. if (destinations.empty()) { err_msg = TRANSLATE("Cannot reach the STUN server: %1"); err_msg = replace_first(err_msg, "%1", user_config->get_stun_server().encode()); err_msg += "\n\n"; err_msg += TRANSLATE("If you are behind a firewall then you need to open the following UDP ports."); err_msg += "\n"; err_msg += TRANSLATE("Port %1 (SIP signaling)"); err_msg = replace_first(err_msg, "%1", int2str(sys_config->get_sip_port())); err_msg += "\n"; err_msg += TRANSLATE("Ports %1-%2 (RTP/RTCP)"); err_msg = replace_first(err_msg, "%1", int2str(sys_config->get_rtp_port())); err_msg = replace_first(err_msg, "%2", int2str(sys_config->get_rtp_port() + 5)); return false; } log_file->write_report("Failover to next destination.", "::stun_discover_nat"); break; case StunTypeFailure: destinations.pop_front(); log_file->write_report("Failover to next destination.", "::stun_discover_nat"); break; default: // Use STUN. return true; } } err_msg = TRANSLATE("NAT type discovery via STUN failed."); return false; } // Main function for STUN listener thread for media STUN requests. void *stun_listen_main(void *arg) { char buf[STUN_MAX_MESSAGE_SIZE + 1]; int data_size; t_socket_udp *sock = (t_socket_udp *)arg; while(true) { try { data_size = sock->recv(buf, STUN_MAX_MESSAGE_SIZE + 1); } catch (int err) { string msg("Failed to receive STUN response for media.\n"); msg += get_error_str(err); log_file->write_report(msg, "::stun_listen_main", LOG_NORMAL, LOG_CRITICAL); // The request will timeout, no need to send a response now. return NULL; } StunMessage m; if (!stunParseMessage(buf, data_size, m, false)) { log_file->write_report("Faulty STUN message", "::stun_listen_main"); continue; } log_file->write_header("::stun_listen_main", LOG_STUN); log_file->write_raw("Received: "); log_file->write_raw(stunMsg2Str(m)); log_file->write_footer(); evq_trans_mgr->push_stun_response(&m, 0, 0); } } ////////////////////////////////////////////// // Base STUN transaction ////////////////////////////////////////////// t_mutex t_stun_transaction::mtx_class; t_tid t_stun_transaction::next_id = 1; t_stun_transaction::t_stun_transaction(t_user *user, StunMessage *r, unsigned short _tuid, const list &dst) { mtx_class.lock(); id = next_id++; if (next_id == 65535) next_id = 1; mtx_class.unlock(); state = TS_NULL; request = new StunMessage(*r); MEMMAN_NEW(request); tuid = _tuid; dur_req_timeout = DUR_STUN_START_INTVAL; num_transmissions = 0; destinations = dst; user_config = user->copy(); } t_stun_transaction::~t_stun_transaction() { MEMMAN_DELETE(request); delete request; MEMMAN_DELETE(user_config); delete user_config; } t_tid t_stun_transaction::get_id(void) const { return id; } t_trans_state t_stun_transaction::get_state(void) const { return state; } void t_stun_transaction::start_timer_req_timeout(void) { timer_req_timeout = transaction_mgr->start_stun_timer(dur_req_timeout, STUN_TMR_REQ_TIMEOUT, id); // RFC 3489 9.3 // Double the retransmision interval till a maximum if (dur_req_timeout < DUR_STUN_MAX_INTVAL) { dur_req_timeout = 2 * dur_req_timeout; } } void t_stun_transaction::stop_timer_req_timeout(void) { if (timer_req_timeout) { transaction_mgr->stop_timer(timer_req_timeout); timer_req_timeout = 0; } } void t_stun_transaction::process_response(StunMessage *r) { stop_timer_req_timeout(); evq_trans_layer->push_stun_response(r, tuid, id); state = TS_TERMINATED; } void t_stun_transaction::process_icmp(const t_icmp_msg &icmp) { stop_timer_req_timeout(); log_file->write_report("Failover to next destination.", "t_stun_transaction::process_icmp"); destinations.pop_front(); if (destinations.empty()) { log_file->write_report("No next destination for failover.", "t_stun_transaction::process_icmp"); log_file->write_header("t_stun_transaction::process_icmp", LOG_NORMAL, LOG_INFO); log_file->write_raw("ICMP error received.\n\n"); log_file->write_raw("Send internal: 500 Server Error\n"); log_file->write_footer(); // No server could be reached, Notify the TU with 500 Server // Error. StunMessage *resp = stunBuildError(*request, 500, "Server Error"); evq_trans_layer->push_stun_response(resp, tuid, id); MEMMAN_DELETE(resp); delete resp; state = TS_TERMINATED; return; } // Failover to next destination evq_sender->push_stun_request(user_config, request, TYPE_STUN_SIP, tuid, id, destinations.front().ipaddr, destinations.front().port); num_transmissions = 1; dur_req_timeout = DUR_STUN_START_INTVAL; start_timer_req_timeout(); } void t_stun_transaction::timeout(t_stun_timer t) { // RFC 3489 9.3 if (num_transmissions < STUN_MAX_TRANSMISSIONS) { retransmit(); start_timer_req_timeout(); return; } // Report timeout to TU StunMessage *timeout_resp; timeout_resp = stunBuildError(*request, 408, "Request Timeout"); log_file->write_report("STUN request timeout", "t_stun_transaction::timeout", LOG_NORMAL); evq_trans_layer->push_stun_response(timeout_resp, tuid, id); MEMMAN_DELETE(timeout_resp); delete timeout_resp; state = TS_TERMINATED; } bool t_stun_transaction::match(StunMessage *resp) const { return stunEqualId(*resp, *request); } // An ICMP error matches a transaction when the destination IP address/port // of the packet that caused the ICMP error equals the destination // IP address/port of the transaction. Other information of the packet causing // the ICMP error is not available. // In theory when multiple transactions are open for the same destination, the // wrong transaction may process the ICMP error. In practice this should rarely // happen as the destination will be unreachable for all those transactions. // If it happens a transaction gets aborted. bool t_stun_transaction::match(const t_icmp_msg &icmp) const { return (destinations.front().ipaddr == icmp.ipaddr && destinations.front().port == icmp.port); } ////////////////////////////////////////////// // SIP STUN transaction ////////////////////////////////////////////// void t_sip_stun_trans::retransmit(void) { // The SIP UDP sender will send out the STUN request. evq_sender->push_stun_request(user_config, request, TYPE_STUN_SIP, tuid, id, destinations.front().ipaddr, destinations.front().port); num_transmissions++; } t_sip_stun_trans::t_sip_stun_trans(t_user *user, StunMessage *r, unsigned short _tuid, const list &dst) : t_stun_transaction(user, r, _tuid, dst) { // The SIP UDP sender will send out the STUN request. evq_sender->push_stun_request(user_config, request, TYPE_STUN_SIP, tuid, id, destinations.front().ipaddr, destinations.front().port); num_transmissions++; start_timer_req_timeout(); state = TS_PROCEEDING; } ////////////////////////////////////////////// // Media STUN transaction ////////////////////////////////////////////// // TODO: this code is not used anymore. Remove? void t_media_stun_trans::retransmit(void) { // Retransmit the STUN request StunAtrString stun_pass; stun_pass.sizeValue = 0; char m[STUN_MAX_MESSAGE_SIZE]; int msg_size = stunEncodeMessage(*request, m, STUN_MAX_MESSAGE_SIZE, stun_pass, false); try { sock->sendto(destinations.front().ipaddr, destinations.front().port, m, msg_size); } catch (int err) { string msg("Failed to send STUN request for media.\n"); msg += get_error_str(err); log_file->write_report(msg, "::t_media_stun_trans::retransmit", LOG_NORMAL, LOG_CRITICAL); StunMessage *resp; resp = stunBuildError(*request, 500, "Could not send request"); evq_trans_layer->push_stun_response(resp, tuid, id); MEMMAN_DELETE(resp); delete resp; return; } num_transmissions++; } t_media_stun_trans::t_media_stun_trans(t_user *user, StunMessage *r, unsigned short _tuid, const list &dst, unsigned short src_port) : t_stun_transaction(user, r, _tuid, dst) { thr_listen = NULL; try { sock = new t_socket_udp(src_port); MEMMAN_NEW(sock); sock->connect(destinations.front().ipaddr, destinations.front().port); } catch (int err) { string msg("Failed to create a UDP socket (STUN) on port "); msg += int2str(src_port); msg += "\n"; msg += get_error_str(err); log_file->write_report(msg, "t_media_stun_trans::t_media_stun_trans", LOG_NORMAL, LOG_CRITICAL); delete sock; sock = NULL; StunMessage *resp; resp = stunBuildError(*request, 500, "Could not create socket"); evq_trans_layer->push_stun_response(resp, tuid, id); MEMMAN_DELETE(resp); delete resp; return; } // Send STUN request StunAtrString stun_pass; stun_pass.sizeValue = 0; char m[STUN_MAX_MESSAGE_SIZE]; int msg_size = stunEncodeMessage(*r, m, STUN_MAX_MESSAGE_SIZE, stun_pass, false); try { sock->send(m, msg_size); } catch (int err) { string msg("Failed to send STUN request for media.\n"); msg += get_error_str(err); log_file->write_report(msg, "::t_media_stun_trans::t_media_stun_trans", LOG_NORMAL, LOG_CRITICAL); StunMessage *resp; resp = stunBuildError(*request, 500, "Failed to send request"); evq_trans_layer->push_stun_response(resp, tuid, id); MEMMAN_DELETE(resp); delete resp; return; } num_transmissions++; try { thr_listen = new t_thread(stun_listen_main, sock); MEMMAN_NEW(thr_listen); } catch (int) { log_file->write_report("Failed to create STUN listener thread.", "::t_media_stun_trans::t_media_stun_trans", LOG_NORMAL, LOG_CRITICAL); delete thr_listen; thr_listen = NULL; StunMessage *resp; resp = stunBuildError(*request, 500, "Failed to create STUN listen thread"); evq_trans_layer->push_stun_response(resp, tuid, id); MEMMAN_DELETE(resp); delete resp; return; } start_timer_req_timeout(); state = TS_PROCEEDING; } t_media_stun_trans::~t_media_stun_trans() { if (sock) { MEMMAN_DELETE(sock); delete sock; } if (thr_listen) { thr_listen->cancel(); thr_listen->join(); MEMMAN_DELETE(thr_listen); delete thr_listen; } } twinkle-1.10.1/src/stun/stun_transaction.h000066400000000000000000000110761277565361200206210ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _STUN_TRANSACTION_H #define _STUN_TRANSACTION_H #include "stun.h" #include "phone_user.h" #include "protocol.h" #include "user.h" #include "transaction.h" #include "threads/mutex.h" #include "threads/thread.h" #include "sockets/socket.h" #include "sockets/url.h" // Create a binding in a NAT. // Returns true on success. Returns false when the STUN server returned // an error. Throws an int exception (containing errno) when some // socket operation fails. bool get_stun_binding(t_user *user_config, unsigned short src_port, unsigned long &mapped_ip, unsigned short &mapped_port, int &err_code, string &err_reason); // Discover the type of NAT and determine is STUN should be used or // wether STUN is useless. // It sets the use_stun attribute of phone if STUN should be used. // Return false if STUN cannot be used. err_msg will contain an // error message that can be displayed to the user. bool stun_discover_nat(t_phone_user *pu, string &err_msg); ////////////////////////////////////////////// // Base STUN transaction ////////////////////////////////////////////// class t_stun_transaction { private: static t_mutex mtx_class; // protect static members static t_tid next_id; // next id to be issued protected: t_tid id; // transaction id unsigned short tuid; // TU id t_trans_state state; // Timer for retransmissions unsigned short timer_req_timeout; // Duration for the next timer start in msec unsigned long dur_req_timeout; // Number of transmissions of the request unsigned short num_transmissions; // Destinations for the request list destinations; // User profile of user that created this transaction t_user *user_config; void start_timer_req_timeout(void); void stop_timer_req_timeout(void); // Retransmit STUN request virtual void retransmit(void) = 0; public: StunMessage *request; t_tid get_id(void) const; // Get state of the transaction t_trans_state get_state(void) const; // The transaction will keep a copy of the request t_stun_transaction(t_user *user, StunMessage *r, unsigned short _tuid, const list &dst); // All request and response pointers contained by the // transaction will be deleted. virtual ~t_stun_transaction(); // Process STUN response virtual void process_response(StunMessage *r); // Process ICMP error virtual void process_icmp(const t_icmp_msg &icmp); // Process timeout virtual void timeout(t_stun_timer t); // Match response with transaction bool match(StunMessage *resp) const; // Match ICMP error with transaction bool match(const t_icmp_msg &icmp) const; }; ////////////////////////////////////////////// // SIP STUN transaction ////////////////////////////////////////////// // A SIP STUN transaction is a STUN request to get a binding // for the SIP port. Such a request must be sent from the SIP port. // So it must be sent out via the SIP sender thread. class t_sip_stun_trans : public t_stun_transaction { protected: virtual void retransmit(void); public: // Create transaction and send out STUN request t_sip_stun_trans(t_user *user, StunMessage *r, unsigned short _tuid, const list &dst); }; ////////////////////////////////////////////// // Media STUN transaction ////////////////////////////////////////////// // TODO: this code is not used anymore. Remove? // A media STUN transaction is a STUN request to get a binding // for a media port. Such a request must be sent from the media // port. class t_media_stun_trans : public t_stun_transaction { private: t_socket_udp *sock; // UDP socket for STUN t_thread *thr_listen; // Listener thread protected: virtual void retransmit(void); public: // Create transaction and send out STUN request t_media_stun_trans(t_user *user, StunMessage *r, unsigned short _tuid, const list &dst, unsigned short src_port); ~t_media_stun_trans(); }; #endif twinkle-1.10.1/src/stun/udp.cxx000066400000000000000000000211771277565361200163710ustar00rootroot00000000000000#include #include #include #include #include #include #include #ifdef WIN32 #include #include #include #else #include #include #include #include #include #include #include #include #include #include #endif #include #include "udp.h" using namespace std; StunSocket openPort( unsigned short port, unsigned int interfaceIp, bool verbose ) { StunSocket fd; fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); if ( fd == INVALID_SOCKET ) { int err = getErrno(); cerr << "Could not create a UDP socket:" << err << endl; return INVALID_SOCKET; } struct sockaddr_in addr; memset((char*) &(addr),0, sizeof((addr))); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(port); if ( (interfaceIp != 0) && ( interfaceIp != 0x100007f ) ) { addr.sin_addr.s_addr = htonl(interfaceIp); if (verbose ) { clog << "Binding to interface " << hex << "0x" << htonl(interfaceIp) << dec << endl; } } if ( bind( fd,(struct sockaddr*)&addr, sizeof(addr)) != 0 ) { int e = getErrno(); switch (e) { case 0: { cerr << "Could not bind socket" << endl; return INVALID_SOCKET; } case EADDRINUSE: { cerr << "Port " << port << " for receiving UDP is in use" << endl; return INVALID_SOCKET; } break; case EADDRNOTAVAIL: { if ( verbose ) { cerr << "Cannot assign requested address" << endl; } return INVALID_SOCKET; } break; default: { cerr << "Could not bind UDP receive port" << "Error=" << e << " " << strerror(e) << endl; return INVALID_SOCKET; } break; } } if ( verbose ) { clog << "Opened port " << port << " with fd " << fd << endl; } assert( fd != INVALID_SOCKET ); return fd; } bool getMessage( StunSocket fd, char* buf, int* len, unsigned int* srcIp, unsigned short* srcPort, bool verbose) { assert( fd != INVALID_SOCKET ); int originalSize = *len; assert( originalSize > 0 ); struct sockaddr_in from; int fromLen = sizeof(from); *len = recvfrom(fd, buf, originalSize, 0, (struct sockaddr *)&from, (socklen_t*)&fromLen); if ( *len == SOCKET_ERROR ) { int err = getErrno(); switch (err) { case ENOTSOCK: cerr << "Error fd not a socket" << endl; break; case ECONNRESET: cerr << "Error connection reset - host not reachable" << endl; break; default: cerr << "StunSocket Error=" << err << endl; } return false; } if ( *len < 0 ) { clog << "socket closed? negative len" << endl; return false; } if ( *len == 0 ) { clog << "socket closed? zero len" << endl; return false; } *srcPort = ntohs(from.sin_port); *srcIp = ntohl(from.sin_addr.s_addr); if ( (*len)+1 >= originalSize ) { if (verbose) { clog << "Received a message that was too large" << endl; } return false; } buf[*len]=0; return true; } bool sendMessage( StunSocket fd, char* buf, int l, unsigned int dstIp, unsigned short dstPort, bool verbose) { assert( fd != INVALID_SOCKET ); int s; if ( dstPort == 0 ) { // sending on a connected port assert( dstIp == 0 ); s = send(fd,buf,l,0); } else { assert( dstIp != 0 ); assert( dstPort != 0 ); struct sockaddr_in to; int toLen = sizeof(to); memset(&to,0,toLen); to.sin_family = AF_INET; to.sin_port = htons(dstPort); to.sin_addr.s_addr = htonl(dstIp); s = sendto(fd, buf, l, 0,(sockaddr*)&to, toLen); } if ( s == SOCKET_ERROR ) { int e = getErrno(); switch (e) { case ECONNREFUSED: case EHOSTDOWN: case EHOSTUNREACH: { // quietly ignore this } break; case EAFNOSUPPORT: { cerr << "err EAFNOSUPPORT in send" << endl; } break; default: { cerr << "err " << e << " " << strerror(e) << " in send" << endl; } } return false; } if ( s == 0 ) { cerr << "no data sent in send" << endl; return false; } if ( s != l ) { if (verbose) { cerr << "only " << s << " out of " << l << " bytes sent" << endl; } return false; } return true; } void initNetwork() { #ifdef WIN32 WORD wVersionRequested = MAKEWORD( 2, 2 ); WSADATA wsaData; int err; err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { // could not find a usable WinSock DLL cerr << "Could not load winsock" << endl; assert(0); // is this is failing, try a different version that 2.2, 1.0 or later will likely work exit(1); } /* Confirm that the WinSock DLL supports 2.2.*/ /* Note that if the DLL supports versions greater */ /* than 2.2 in addition to 2.2, it will still return */ /* 2.2 in wVersion since that is the version we */ /* requested. */ if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { /* Tell the user that we could not find a usable */ /* WinSock DLL. */ WSACleanup( ); cerr << "Bad winsock verion" << endl; assert(0); // is this is failing, try a different version that 2.2, 1.0 or later will likely work exit(1); } #endif } /* ==================================================================== * The Vovida Software License, Version 1.0 * * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The names "VOCAL", "Vovida Open Communication Application Library", * and "Vovida Open Communication Application Library (VOCAL)" must * not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact vocal@vovida.org. * * 4. Products derived from this software may not be called "VOCAL", nor * may "VOCAL" appear in their name, without prior written * permission of Vovida Networks, Inc. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * ==================================================================== * * This software consists of voluntary contributions made by Vovida * Networks, Inc. and many individuals on behalf of Vovida Networks, * Inc. For more information on Vovida Networks, Inc., please see * . * */ // Local Variables: // mode:c++ // c-file-style:"ellemtel" // c-file-offsets:((case-label . +)) // indent-tabs-mode:nil // End: twinkle-1.10.1/src/stun/udp.h000066400000000000000000000123751277565361200160160ustar00rootroot00000000000000#ifndef udp_h #define udp_h #if defined(__APPLE__) && defined(__MACH__) typedef int socklen_t; #endif #include #ifdef WIN32 #include #include typedef int socklen_t; typedef SOCKET StunSocket; #define EWOULDBLOCK WSAEWOULDBLOCK #define EINPROGRESS WSAEINPROGRESS #define EALREADY WSAEALREADY #define ENOTSOCK WSAENOTSOCK #define EDESTADDRREQ WSAEDESTADDRREQ #define EMSGSIZE WSAEMSGSIZE #define EPROTOTYPE WSAEPROTOTYPE #define ENOPROTOOPT WSAENOPROTOOPT #define EPROTONOSUPPORT WSAEPROTONOSUPPORT #define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT #define EOPNOTSUPP WSAEOPNOTSUPP #define EPFNOSUPPORT WSAEPFNOSUPPORT #define EAFNOSUPPORT WSAEAFNOSUPPORT #define EADDRINUSE WSAEADDRINUSE #define EADDRNOTAVAIL WSAEADDRNOTAVAIL #define ENETDOWN WSAENETDOWN #define ENETUNREACH WSAENETUNREACH #define ENETRESET WSAENETRESET #define ECONNABORTED WSAECONNABORTED #define ECONNRESET WSAECONNRESET #define ENOBUFS WSAENOBUFS #define EISCONN WSAEISCONN #define ENOTCONN WSAENOTCONN #define ESHUTDOWN WSAESHUTDOWN #define ETOOMANYREFS WSAETOOMANYREFS #define ETIMEDOUT WSAETIMEDOUT #define ECONNREFUSED WSAECONNREFUSED #define ELOOP WSAELOOP #define EHOSTDOWN WSAEHOSTDOWN #define EHOSTUNREACH WSAEHOSTUNREACH #define EPROCLIM WSAEPROCLIM #define EUSERS WSAEUSERS #define EDQUOT WSAEDQUOT #define ESTALE WSAESTALE #define EREMOTE WSAEREMOTE typedef LONGLONG Int64; inline int getErrno() { return WSAGetLastError(); } #else typedef int StunSocket; static const StunSocket INVALID_SOCKET = -1; static const int SOCKET_ERROR = -1; inline int closesocket( StunSocket fd ) { return close(fd); }; inline int getErrno() { return errno; } #define WSANOTINITIALISED EPROTONOSUPPORT #endif /// Open a UDP socket to receive on the given port - if port is 0, pick a a /// port, if interfaceIp!=0 then use ONLY the interface specified instead of /// all of them StunSocket openPort( unsigned short port, unsigned int interfaceIp, bool verbose); /// recive a UDP message bool getMessage( StunSocket fd, char* buf, int* len, unsigned int* srcIp, unsigned short* srcPort, bool verbose); /// send a UDP message bool sendMessage( StunSocket fd, char* msg, int len, unsigned int dstIp, unsigned short dstPort, bool verbose); /// set up network - does nothing in unix but needed for windows void initNetwork(); /* ==================================================================== * The Vovida Software License, Version 1.0 * * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The names "VOCAL", "Vovida Open Communication Application Library", * and "Vovida Open Communication Application Library (VOCAL)" must * not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact vocal@vovida.org. * * 4. Products derived from this software may not be called "VOCAL", nor * may "VOCAL" appear in their name, without prior written * permission of Vovida Networks, Inc. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * ==================================================================== * * This software consists of voluntary contributions made by Vovida * Networks, Inc. and many individuals on behalf of Vovida Networks, * Inc. For more information on Vovida Networks, Inc., please see * . * */ // Local Variables: // mode:c++ // c-file-style:"ellemtel" // c-file-offsets:((case-label . +)) // indent-tabs-mode:nil // End: #endif twinkle-1.10.1/src/sub_refer.cpp000066400000000000000000000213051277565361200165350ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "sub_refer.h" #include "line.h" #include "log.h" #include "phone_user.h" #include "user.h" #include "userintf.h" #include "audits/memman.h" t_dialog *t_sub_refer::get_dialog(void) const { return dynamic_cast(dialog); } t_sub_refer::t_sub_refer(t_dialog *_dialog, t_subscription_role _role) : t_subscription(_dialog, _role, SIP_EVENT_REFER) { // A refer subscription is implicitly defined by the REFER // transaction state = SS_ESTABLISHED; // Start the subscription timer only for a notifier. // The subscriber will start a timer when it receives NOTIFY. if (role == SR_NOTIFIER) { unsigned long dur; if (user_config->get_ask_user_to_refer()) { dur = DUR_REFER_SUB_INTERACT * 1000; } else { dur = DUR_REFER_SUBSCRIPTION * 1000; } start_timer(STMR_SUBSCRIPTION, dur); } auto_refresh = user_config->get_auto_refresh_refer_sub(); subscription_expiry = DUR_REFER_SUBSCRIPTION; sr_result = SRR_INPROG; last_response = NULL; log_file->write_header("t_sub_refer::t_sub_refer"); log_file->write_raw("Refer "); if (role == SR_SUBSCRIBER) { log_file->write_raw("subscriber"); } else { log_file->write_raw("notifier"); } log_file->write_raw(" created: event = "); log_file->write_raw(event_type); log_file->write_endl(); log_file->write_footer(); } t_sub_refer::t_sub_refer(t_dialog *_dialog, t_subscription_role _role, const string &_event_id) : t_subscription(_dialog, _role, SIP_EVENT_REFER, _event_id) { state = SS_ESTABLISHED; if (role == SR_NOTIFIER) { unsigned long dur; if (user_config->get_ask_user_to_refer()) { dur = DUR_REFER_SUB_INTERACT * 1000; } else { dur = DUR_REFER_SUBSCRIPTION * 1000; } start_timer(STMR_SUBSCRIPTION, dur); } auto_refresh = user_config->get_auto_refresh_refer_sub(); subscription_expiry = DUR_REFER_SUBSCRIPTION; sr_result = SRR_INPROG; last_response = NULL; log_file->write_header("t_sub_refer::t_sub_refer"); log_file->write_raw("Refer "); if (role == SR_SUBSCRIBER) { log_file->write_raw("subscriber"); } else { log_file->write_raw("notifier"); } log_file->write_raw(" created: event = "); log_file->write_raw(event_type); log_file->write_raw(";id="); log_file->write_raw(event_id); log_file->write_endl(); log_file->write_footer(); } t_sub_refer::~t_sub_refer() { if (last_response) { MEMMAN_DELETE(last_response); delete last_response; } log_file->write_header("t_sub_refer::~t_sub_refer"); log_file->write_raw("Refer "); if (role == SR_SUBSCRIBER) { log_file->write_raw("subscriber"); } else { log_file->write_raw("notifier"); } log_file->write_raw(" destroyed: event = "); log_file->write_raw(event_type); if (!event_id.empty()) { log_file->write_raw(";id="); log_file->write_raw(event_id); } log_file->write_endl(); log_file->write_footer(); } void t_sub_refer::send_notify(t_response *r, const string &substate, const string reason) { t_request *notify; if (substate == SUBSTATE_TERMINATED) { // RFC 3515 2.4.7 notify = create_notify(substate, reason); stop_timer(STMR_SUBSCRIPTION); } else { notify = create_notify(substate); } // RFC 3515 2.4.4 // Create message/sipfrag body containing only the status line // of the response. t_response sipfrag(r->code, r->reason); notify->body = new t_sip_body_sipfrag(&sipfrag); MEMMAN_NEW(notify->body); notify->hdr_content_type.set_media(t_media("message", "sipfrag")); // If an outgoing NOTIFY is still pending, then store this // NOTIFY in the queue if (req_out) { queue_notify.push(notify); } else { // Send NOTIFY req_out = new t_client_request(user_config, notify,0); MEMMAN_NEW(req_out); send_request(user_config, notify, req_out->get_tuid()); MEMMAN_DELETE(notify); delete notify; } // Keep response and state such that it can be resend when // a SUBSCRIBE is received. if (last_response && last_response != r) { MEMMAN_DELETE(last_response); delete last_response; last_response = NULL; } if (!last_response) last_response = (t_response *)r->copy(); current_substate = substate; } bool t_sub_refer::recv_notify(t_request *r, t_tuid tuid, t_tid tid) { if (t_subscription::recv_notify(r, tuid, tid)) return true; // RFC 3515 2.4.5. // NOTIFY must have a sipfrag body if (!r->body || r->body->get_type() != BODY_SIPFRAG) { t_response *resp = r->create_response(R_400_BAD_REQUEST, "message/sipfrag body missing"); send_response(user_config, resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return true; } // RFC 3515 2.4.5 // The sipfrag body must start with a Status-Line if (((t_sip_body_sipfrag *)r->body)->sipfrag->get_type() != MSG_RESPONSE) { t_response *resp = r->create_response(R_400_BAD_REQUEST, "sipfrag body does not begin with Status-Line"); send_response(user_config, resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return true; } t_response *sipfrag = (t_response *)((t_sip_body_sipfrag *)r->body)->sipfrag; // Determine state of reference if (r->hdr_subscription_state.substate == SUBSTATE_TERMINATED) { if (r->hdr_subscription_state.reason == EV_REASON_REJECTED) { // Referee rejected to refer sr_result = SRR_FAILED; } else if (r->hdr_subscription_state.reason == EV_REASON_NORESOURCE) { // Reference is finished. The sipfrag body indicates // success or failure. if (sipfrag->is_success()) { sr_result = SRR_SUCCEEDED; } else { sr_result = SRR_FAILED; } } } // Inform user about progress ui->cb_notify_recvd(get_dialog()->get_line()->get_line_number(), r); t_response *resp = r->create_response(R_200_OK); send_response(user_config, resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return true; } bool t_sub_refer::recv_subscribe(t_request *r, t_tuid tuid, t_tid tid) { unsigned long expires; if (t_subscription::recv_subscribe(r, tuid, tid)) return true; // Determine value for Expires header if (!r->hdr_expires.is_populated() || r->hdr_expires.time > 2 * DUR_REFER_SUBSCRIPTION) { // User did not indicate an expiry time for subscription // refresh or a time larger then 2 times the default. // Just use the Twinkle default. stop_timer(STMR_SUBSCRIPTION); start_timer(STMR_SUBSCRIPTION, DUR_REFER_SUBSCRIPTION * 1000); expires = DUR_REFER_SUBSCRIPTION; } else { expires = r->hdr_expires.time; } t_response *resp = r->create_response(R_200_OK); // RFC 3265 7.1 // Contact header is mandatory t_contact_param contact; contact.uri.set_url(get_dialog()->get_line()->create_user_contact( h_ip2str(resp->get_local_ip()))); resp->hdr_contact.add_contact(contact); // Expires header is mandatory resp->hdr_expires.set_time(expires); send_response(user_config, resp, 0, tid); MEMMAN_DELETE(resp); delete resp; // RFC 3265 3.2.2 // After a successful SUBSCRIBE the notifier must immediately // send a NOTIFY. // If no last response has been kept then this is probably a // first SUBSCRIBE. The dialog has to send the initial NOTIFY. if (last_response) { if (expires == 0) { send_notify(last_response, SUBSTATE_TERMINATED, EV_REASON_TIMEOUT); } else { send_notify(last_response, current_substate); } } return true; } bool t_sub_refer::timeout(t_subscribe_timer timer) { if (t_subscription::timeout(timer)) return true; switch (timer) { case STMR_SUBSCRIPTION: switch (role) { case SR_NOTIFIER: // RFC 3265 3.1.6.4 // The subscription has expired // RFC 2.4.5 // Each NOTIFY MUST contain a body if (last_response) { // Repeat last response as body send_notify(last_response, SUBSTATE_TERMINATED, EV_REASON_TIMEOUT); } else { // This should never happen. Create a timeout // response for the body. t_response resp(R_408_REQUEST_TIMEOUT); send_notify(&resp, SUBSTATE_TERMINATED, EV_REASON_TIMEOUT); } log_file->write_report("Refer notifier timed out.", "t_sub_refer::timeout"); return true; case SR_SUBSCRIBER: // Should have been handled by parent class default: assert(false); } break; default: assert(false); } return false; } t_sub_refer_result t_sub_refer::get_sr_result(void) const { return sr_result; } twinkle-1.10.1/src/sub_refer.h000066400000000000000000000037451277565361200162120ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // RFC 3515 // Refer event package #ifndef _SUB_REFER_H #define _SUB_REFER_H #include "subscription.h" #include "dialog.h" // State of reference as seen by the referrer enum t_sub_refer_result { SRR_INPROG, // Referee is referring call SRR_FAILED, // Refer failed SRR_SUCCEEDED, // Refer succeeded }; class t_sub_refer : public t_subscription { private: // Result of the reference as seen by the referrer. t_sub_refer_result sr_result; // Last response received from the refer-target t_response *last_response; // Current substate of the notification string current_substate; t_dialog *get_dialog(void) const; public: t_sub_refer(t_dialog *_dialog, t_subscription_role _role); t_sub_refer(t_dialog *_dialog, t_subscription_role _role, const string &_event_id); virtual ~t_sub_refer(); // Send a NOTIFY with the status line of the response as body // substate indicates the subscription state of refer // A reason should be given if substate == TERMINATED void send_notify(t_response *r, const string &substate, const string reason = ""); bool recv_notify(t_request *r, t_tuid tuid, t_tid tid); bool recv_subscribe(t_request *r, t_tuid tuid, t_tid tid); bool timeout(t_subscribe_timer timer); t_sub_refer_result get_sr_result(void) const; }; #endif twinkle-1.10.1/src/subscription.cpp000066400000000000000000000452251277565361200173140ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "subscription.h" #include "dialog.h" #include "line.h" #include "log.h" #include "phone_user.h" #include "audits/memman.h" #include "parser/hdr_event.h" extern t_event_queue *evq_trans_mgr; extern t_event_queue *evq_timekeeper; extern t_timekeeper *timekeeper; string t_subscription_state2str(t_subscription_state state) { switch (state) { case SS_NULL: return "SS_NULL"; case SS_ESTABLISHED: return "SS_ESTABLISHED"; case SS_UNSUBSCRIBING: return "SS_UNSUBSCRIBING"; case SS_UNSUBSCRIBED: return "SS_UNSUBSCRIBED"; case SS_TERMINATED: return "SS_TERMINATED"; } return "UNKNOWN"; } ///////////// // PROTECTED ///////////// void t_subscription::log_event() const { log_file->write_raw("Event: "); log_file->write_raw(event_type); log_file->write_endl(); log_file->write_raw("Event id: "); log_file->write_raw(event_id); log_file->write_endl(); } void t_subscription::remove_client_request(t_client_request **cr) { if ((*cr)->dec_ref_count() == 0) { MEMMAN_DELETE(*cr); delete *cr; } *cr = NULL; } t_request *t_subscription::create_subscribe(unsigned long expires) const { // RFC 3265 3.1.4 t_request *r = dialog->create_request(SUBSCRIBE); r->hdr_expires.set_time(expires); r->hdr_event.set_event_type(event_type); if (event_id.size() > 0) r->hdr_event.set_id(event_id); // Re-calculate the destination as the event type may // influence the route to be taken. // The destination has been calculated already at the // dialog level. r->calc_destinations(*user_config); return r; } t_request *t_subscription::create_notify(const string &sub_state, const string &reason) const { // RFC 3265 3.2.2 t_request *r = dialog->create_request(NOTIFY); r->hdr_event.set_event_type(event_type); if (event_id.size() > 0) r->hdr_event.set_id(event_id); r->hdr_subscription_state.set_substate(sub_state); // Subscription state specific parameters if (sub_state == SUBSTATE_ACTIVE || sub_state == SUBSTATE_PENDING) { // Add expires parameter with remaining time if (id_subscription_timeout) { long remaining = timekeeper-> get_remaining_time(id_subscription_timeout); r->hdr_subscription_state.set_expires(remaining / 1000); } } else if (sub_state == SUBSTATE_TERMINATED) { // Add reason parameter if (reason.size() > 0) { r->hdr_subscription_state.set_reason(reason); } } return r; } void t_subscription::send_request(t_user *user_config, t_request *r, t_tuid tuid) const { evq_trans_mgr->push_user(user_config, (t_sip_message *)r, tuid, 0); } void t_subscription::send_response(t_user *user_config, t_response *r, t_tuid tuid, t_tid tid) const { evq_trans_mgr->push_user(user_config, (t_sip_message *)r, tuid, tid); } void t_subscription::start_timer(t_subscribe_timer timer, long duration) { t_tmr_subscribe *t; t_object_id oid_line = 0; switch(timer) { case STMR_SUBSCRIPTION: if (dynamic_cast(dialog) != NULL) { oid_line = dynamic_cast(dialog)->get_line()->get_object_id(); } t = new t_tmr_subscribe(duration, timer, oid_line, dialog->get_object_id(), event_type, event_id); MEMMAN_NEW(t); id_subscription_timeout = t->get_object_id(); break; default: assert(false); } evq_timekeeper->push_start_timer(t); MEMMAN_DELETE(t); delete t; } void t_subscription::stop_timer(t_subscribe_timer timer) { unsigned short *id; switch(timer) { case STMR_SUBSCRIPTION: id = &id_subscription_timeout; break; default: assert(false); } if (*id != 0) evq_timekeeper->push_stop_timer(*id); *id = 0; } ////////// // PUBLIC ////////// t_subscription::t_subscription(t_abstract_dialog *_dialog, t_subscription_role _role, const string &_event_type) { dialog = _dialog; user_config = dialog->get_user(); assert(user_config); role = _role; state = SS_NULL; resubscribe_after = 0; may_resubscribe = false; pending = true; id_subscription_timeout = 0; req_out = NULL; event_type = _event_type; auto_refresh = true; subscription_expiry = 3600; default_duration = 3600; } t_subscription::t_subscription(t_abstract_dialog *_dialog, t_subscription_role _role, const string &_event_type, const string &_event_id) { dialog = _dialog; user_config = dialog->get_user(); assert(user_config); role = _role; state = SS_NULL; resubscribe_after = 0; may_resubscribe = false; pending = true; id_subscription_timeout = 0; req_out = NULL; event_type = _event_type; event_id = _event_id; auto_refresh = true; subscription_expiry = 3600; default_duration = 3600; } t_subscription::~t_subscription() { if (req_out) remove_client_request(&req_out); if (id_subscription_timeout) stop_timer(STMR_SUBSCRIPTION); // Cleanup list of unsent NOTIFY messages while (!queue_notify.empty()) { t_request *r = queue_notify.front(); queue_notify.pop(); MEMMAN_DELETE(r); delete r; } } t_subscription_role t_subscription::get_role(void) const { return role; } t_subscription_state t_subscription::get_state(void) const { return state; } string t_subscription::get_reason_termination(void) const { return reason_termination; } unsigned long t_subscription::get_resubscribe_after(void) const { return resubscribe_after; } bool t_subscription::get_may_resubscribe(void) const { return may_resubscribe; } string t_subscription::get_event_type(void) const { return event_type; } string t_subscription::get_event_id(void) const { return event_id; } unsigned long t_subscription::get_expiry(void) const { return subscription_expiry; } bool t_subscription::recv_subscribe(t_request *r, t_tuid tuid, t_tid tid) { if (role != SR_NOTIFIER) { // Reject a SUBSCRIBE coming in for a SUBSCRIBER // TODO: is this ok?? log_file->write_header("t_subscription::recv_subscribe", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("SUBSCRIBER receives SUBSCRIBE.\n"); log_event(); log_file->write_endl(); log_file->write_raw(r->encode()); log_file->write_endl(); log_file->write_footer(); t_response *resp = r->create_response(R_603_DECLINE); send_response(user_config, resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return true; } // If the subscription is already in the terminated state // then a SUBSCRIBE is not allowed anymore. if (state == SS_TERMINATED) { t_response *resp = r->create_response(R_481_TRANSACTION_NOT_EXIST, REASON_481_SUBSCRIPTION_NOT_EXIST); send_response(user_config, resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return true; } if (state == SS_NULL) state = SS_ESTABLISHED; // If there is no expires header, then the implementation of // the event specific package must deal with this subscribe if (!r->hdr_expires.is_populated()) { return false; } // An expiry time of 0 is an unsubscribe if (r->hdr_expires.time == 0) { stop_timer(STMR_SUBSCRIPTION); state = SS_TERMINATED; return false; } // Check if the requested expiry is not too small if (r->hdr_expires.time < MIN_DUR_SUBSCRIPTION) { t_response *resp = r->create_response( R_423_INTERVAL_TOO_BRIEF); resp->hdr_min_expires.set_time(MIN_DUR_SUBSCRIPTION); send_response(user_config, resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return true; } // Restart subscription timer stop_timer(STMR_SUBSCRIPTION); start_timer(STMR_SUBSCRIPTION, r->hdr_expires.time * 1000); return false; } bool t_subscription::recv_notify(t_request *r, t_tuid tuid, t_tid tid) { if (role != SR_SUBSCRIBER) { // Reject a NOTIFY coming in for a NOTIFIER // TODO: is this ok?? log_file->write_header("t_subscription::recv_notify", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("NOTIFIER receives NOTIFY.\n"); log_event(); log_file->write_endl(); log_file->write_raw(r->encode()); log_file->write_endl(); log_file->write_footer(); t_response *resp = r->create_response(R_603_DECLINE); send_response(user_config, resp, 0, tid); MEMMAN_DELETE(resp); delete resp; return true; } if (state == SS_NULL) { log_file->write_header("t_subscription::recv_notify", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("NOTIFY establishes subscription.\n"); log_event(); log_file->write_footer(); state = SS_ESTABLISHED; } if (r->hdr_subscription_state.substate == SUBSTATE_ACTIVE && pending) { log_file->write_header("t_subscription::recv_notify", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("NOTIFY ends pending state.\n"); log_event(); log_file->write_footer(); pending = false; } if (r->hdr_subscription_state.substate == SUBSTATE_TERMINATED) { stop_timer(STMR_SUBSCRIPTION); state = SS_TERMINATED; reason_termination = r->hdr_subscription_state.reason; resubscribe_after = r->hdr_subscription_state.retry_after; // RFC 3264 3.2.4 if (resubscribe_after > 0) { may_resubscribe = true; } else { if (reason_termination == EV_REASON_DEACTIVATED || reason_termination == EV_REASON_TIMEOUT) { may_resubscribe = true; } else if (reason_termination == EV_REASON_PROBATION || reason_termination == EV_REASON_GIVEUP) { may_resubscribe = true; resubscribe_after = DUR_RESUBSCRIBE; } } log_file->write_header("t_subscription::recv_notify", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("NOTIFY terminates subscription.\n"); log_event(); log_file->write_raw("Termination reason: "); log_file->write_raw(reason_termination); log_file->write_endl(); log_file->write_raw("May resubscribe: "); log_file->write_bool(may_resubscribe); log_file->write_endl(); log_file->write_raw("Resubscribe after: "); log_file->write_raw(resubscribe_after); log_file->write_endl(); log_file->write_footer(); } if (r->hdr_subscription_state.expires > 0 && state == SS_ESTABLISHED) { log_file->write_header("t_subscription::recv_notify", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Received NOTIFY on established subscription.\n"); log_event(); log_file->write_footer(); unsigned long dur = r->hdr_subscription_state.expires; if (auto_refresh) { if (!id_subscription_timeout || timekeeper->get_remaining_time(id_subscription_timeout) >= dur * 1000) { // Adjust timer to expiry duration indicated // in NOTIFY. dur -= dur / 10; stop_timer(STMR_SUBSCRIPTION); start_timer(STMR_SUBSCRIPTION, dur * 1000); } } else { if (!id_subscription_timeout || timekeeper->get_remaining_time(id_subscription_timeout) < dur * 1000) { // Adjust timer to expiry duration indicated // in NOTIFY. dur += dur / 10; stop_timer(STMR_SUBSCRIPTION); start_timer(STMR_SUBSCRIPTION, dur * 1000); } } } return false; } bool t_subscription::recv_response(t_response *r, t_tuid tuid, t_tid tid) { switch (r->hdr_cseq.method) { case NOTIFY: return recv_notify_response(r, tuid, tid); break; case SUBSCRIBE: return recv_subscribe_response(r, tuid, tid); break; default: break; } return false; } bool t_subscription::recv_notify_response(t_response *r, t_tuid tuid, t_tid tid) { // Discard response if it does not match a pending request if (!req_out) return true; if (r->hdr_cseq.method != req_out->get_request()->method) return true; // Ignore provisional responses if (r->is_provisional()) return true; // Successful response if (r->is_success()) { if (req_out->get_request()->hdr_subscription_state.substate == SUBSTATE_TERMINATED) { // This is a 2XX respsone on a NOTIFY that terminates the // subscription. state = SS_TERMINATED; log_file->write_header("t_subscription::recv_notify_response", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Subscription terminated by 2XX NOTIFY.\n"); log_file->write_raw(r->code); log_file->write_raw(" " + r->reason + "\n"); log_event(); log_file->write_footer(); } remove_client_request(&req_out); } else { // RFC 3265 3.2.2 // NOTIFY failed, terminate subscription remove_client_request(&req_out); state = SS_TERMINATED; log_file->write_header("t_subscription::recv_notify_response", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Subscription terminated by NOTIFY failure response.\n"); log_file->write_raw(r->code); log_file->write_raw(" " + r->reason + "\n"); log_event(); log_file->write_footer(); return true; } // If there is a NOTIFY in the queue, then send it if (!queue_notify.empty()) { log_file->write_header("t_subscription::recv_notify_response", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Get NOTIFY from queue."); log_event(); log_file->write_footer(); t_request *notify = queue_notify.front(); queue_notify.pop(); req_out = new t_client_request(user_config, notify,0); MEMMAN_NEW(req_out); send_request(user_config, notify, req_out->get_tuid()); MEMMAN_DELETE(notify); delete notify; } return true; } bool t_subscription::recv_subscribe_response(t_response *r, t_tuid tuid, t_tid tid) { // Discard response if it does not match a pending request if (!req_out) return true; if (r->hdr_cseq.method != req_out->get_request()->method) return true; // Ignore provisional responses if (r->is_provisional()) return true; // Successful response if (r->is_success()) { if (state == SS_NULL) { log_file->write_header("t_subscription::recv_subscribe_response", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Subscription established by 2XX SUBSCRIBE.\n"); log_file->write_raw(r->code); log_file->write_raw(" " + r->reason + "\n"); log_event(); log_file->write_footer(); state = SS_ESTABLISHED; } // RFC 3265 7.1, 7.2 says that the Expires header is mandatory // in a 2XX response. Some SIP servers do not include this // however. To interoperate with such servers, assume that // the granted expiry time equals the requested expiry time. if (!r->hdr_expires.is_populated()) { r->hdr_expires.set_time( req_out->get_request()->hdr_expires.time); log_file->write_header( "t_subscription::recv_subscribe_response", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Mandatory Expires header missing.\n"); log_file->write_raw("Assuming expires = "); log_file->write_raw(r->hdr_expires.time); log_file->write_endl(); log_event(); log_file->write_footer(); } // If some faulty server sends a non-zero expiry time in // a response on an unsubscribe request, then ignore // the expiry time. if (r->hdr_expires.time == 0 || state == SS_UNSUBSCRIBING) { // Unsubscription succeeded. stop_timer(STMR_SUBSCRIPTION); // Start the unsubscribe guard. If the triggered // NOTIFY is never received, this guard assures, that // the subscription will be cleaned up at timeout. start_timer(STMR_SUBSCRIPTION, DUR_UNSUBSCRIBE_GUARD); // The subscription will only // terminate after a NOTIFY triggered by the unsubscribe // has been received or this guard timer expires. state = SS_UNSUBSCRIBED; auto_refresh = false; log_file->write_header("t_subscription::recv_subscribe_response", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Unsubcription successful.\n"); log_event(); log_file->write_footer(); } else { // Start/refresh subscribe timer stop_timer(STMR_SUBSCRIPTION); unsigned long dur = r->hdr_expires.time; if (auto_refresh) { dur -= dur / 10; } else { dur += dur / 10; } start_timer(STMR_SUBSCRIPTION, dur * 1000); } remove_client_request(&req_out); return true; } // RFC 3265 3.1.4.1 // SUBSCRIBE failed, terminate subscription // NOTE: redirection and authentication responses should have // been handled already (eg. on line or phone user level). remove_client_request(&req_out); state = SS_TERMINATED; log_file->write_header("t_subscription::recv_subscribe_response", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Subscription terminated by SUBSCRIBE failure response.\n"); log_file->write_raw(r->code); log_file->write_raw(" " + r->reason + "\n"); log_event(); log_file->write_footer(); return true; } bool t_subscription::timeout(t_subscribe_timer timer) { switch (timer) { case STMR_SUBSCRIPTION: id_subscription_timeout = 0; if (role == SR_SUBSCRIBER) { log_file->write_header("t_subcription::timeout"); log_file->write_raw("Subscriber timed out.\n"); log_event(); log_file->write_footer(); if (auto_refresh) { // Refresh subscription refresh_subscribe(); } else { // The cause for timeout may be temporary. // Allow resubscription to overcome a transient problem. may_resubscribe = true; state = SS_TERMINATED; } return true; } break; default: assert(false); } return false; } bool t_subscription::match_timer(t_subscribe_timer timer, t_object_id id_timer) const { return id_timer == id_subscription_timeout; } bool t_subscription::match(t_request *r) const { if (!r->hdr_event.is_populated()) return false; // If the subscription has been terminated, then do not match // any request. if (state == SS_TERMINATED) return false; return (r->hdr_event.event_type == event_type && r->hdr_event.id == event_id); } bool t_subscription::is_pending(void) const { return pending; } void t_subscription::subscribe(unsigned long expires) { if (req_out) { // Delete previous outgoing request MEMMAN_DELETE(req_out); delete req_out; } if (expires > 0) { subscription_expiry = expires; } else { subscription_expiry = default_duration; } t_request *r = create_subscribe(subscription_expiry); req_out = new t_client_request(user_config, r ,0); MEMMAN_NEW(req_out); send_request(user_config, r, req_out->get_tuid()); MEMMAN_DELETE(r); delete r; } void t_subscription::unsubscribe(void) { if (state != SS_ESTABLISHED) { state = SS_TERMINATED; return; } if (req_out) { // Delete previous outgoing request MEMMAN_DELETE(req_out); delete req_out; } stop_timer(STMR_SUBSCRIPTION); t_request *r = create_subscribe(0); req_out = new t_client_request(user_config, r ,0); MEMMAN_NEW(req_out); send_request(user_config, r, req_out->get_tuid()); MEMMAN_DELETE(r); delete r; // NOTE: the subscription is only ended when the response is received. state = SS_UNSUBSCRIBING; } void t_subscription::refresh_subscribe(void) { if (state != SS_ESTABLISHED) return; stop_timer(STMR_SUBSCRIPTION); subscribe(subscription_expiry); } twinkle-1.10.1/src/subscription.h000066400000000000000000000225011277565361200167510ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Subscription (RFC 3265) */ #ifndef _SUBSCRIPTION_H #define _SUBSCRIPTION_H #include #include #include "abstract_dialog.h" /** Subscription role */ enum t_subscription_role { SR_SUBSCRIBER, /**< Subscriber */ SR_NOTIFIER /**< Notifier */ }; /** Subscription state */ enum t_subscription_state { SS_NULL, /**< Initial state */ SS_ESTABLISHED, /**< Subscription is in place */ SS_UNSUBSCRIBING, /**< A request to unsubscribe has been sent */ SS_UNSUBSCRIBED, /**< An outoging unsubscribe was successful. Waiting for the final NOTIFY. */ SS_TERMINATED, /**< Subscription ended */ }; /** * Convert a subscription state to string. * @param state [in] Subscription state. * @return String representation of state. */ string t_subscription_state2str(t_subscription_state state); /** * RFC 3265 * Generic subscription state for subscribers and notifiers * For each event type this class should be subclassed. */ class t_subscription { protected: t_subscription_role role; t_subscription_state state; /** * When a subscriber subscription is terminated, this reason indicates * the reason conveyed in the NOTIFY, if any. */ string reason_termination; /** * If the NOTIFY indicated that the subscriber may retry subscription at * a later time, then resubscribe_after indicates the number of seconds to wait. */ unsigned long resubscribe_after; /** Indicates if a re-subscribe may be done after a failure. */ bool may_resubscribe; t_abstract_dialog *dialog; /**< Dialog owning the subscription */ string event_type; string event_id; /** * User profile of user using the line * This is a pointer to the user_config owned by a phone user. * So this pointer should never be deleted. */ t_user *user_config; bool pending; /**< Indicates if not active yet. */ /** @name Timers */ //@{ /** * For a subscriber the subscription_timeout timer indicates when * the subscription must be refreshed. * For a notifier it indicates when the subscription expires. */ unsigned short id_subscription_timeout; /** * Indicates if a subscriber automatically refreshes the subscritption * when the subscription timer expires. If not, then the subscription * terminates at expiry. */ bool auto_refresh; /** Subcription expiry for a SUBSCRIBE request */ unsigned long subscription_expiry; /** Default duration for a subscription */ unsigned long default_duration; //@} /** Protect constructor from being used */ t_subscription() {}; /** Write event type and id to log file */ void log_event() const; /** * Remove a pending request. Pass one of the client request pointers. * @param cr [in] Client request to remove. */ void remove_client_request(t_client_request **cr); /** @name Create requests based on the event type */ //@{ /** * Create a SUBSCRIBE request. * Creating a SUBSCRIBE is for subscription refreshment/unsubscribe. * @param expires [in] Expiry time in seconds. */ virtual t_request *create_subscribe(unsigned long expires) const; /** * Create a NOTIFY request. * @param sub_state [in] Subscription state to be put in the Subscription-State header. * @param reason [in] The reason parameter of the Subscription-State header. */ virtual t_request *create_notify(const string &sub_state, const string &reason = "") const; //@} /** * Send request. * @param user_config [in] User profile of user sending the request. * @param r [in] Request to send. * @param tuid [in] Transaction user id. */ void send_request(t_user *user_config, t_request *r, t_tuid tuid) const; /** * Send response. * @param user_config [in] User profile of user sending the response. * @param r [in] Response to send. * @param tuid [in] Transaction user id. * @param tid [in] Transaction id. */ void send_response(t_user *user_config, t_response *r, t_tuid tuid, t_tid tid) const; /** * Start a subscription timer. * @param timer [in] Type of subscription timer. * @param duration [in] Duration of timer in ms */ void start_timer(t_subscribe_timer timer, long duration); /** * Stop a subscription timer. * @param timer [in] Type of subscription timer. */ void stop_timer(t_subscribe_timer timer); public: /** Pending request */ t_client_request *req_out; /** * Queue of pending outgoing NOTIFY requests. A next NOTIFY * will only be sent after the previous NOTIFY has been * answered. */ queue queue_notify; /** * Constructor * @param _dialog [in] Dialog owning this subscription. SUBSCRIBE and NOTIFY * requests are sent within this dialog. * @param _role [in] Role * @param _event_type [in] Event type of the subscription. */ t_subscription(t_abstract_dialog *_dialog, t_subscription_role _role, const string &_event_type); /** * Constructor * @param _dialog [in] Dialog owning this subscription. SUBSCRIBE and NOTIFY * requests are sent within this dialog. * @param _role [in] Role * @param _event_type [in] Event type of the subscription. * @param _event_id [in] Event id. */ t_subscription(t_abstract_dialog *_dialog, t_subscription_role _role, const string &_event_type, const string &_event_id); /** Destructor */ virtual ~t_subscription(); /** @name Getters */ //@{ t_subscription_role get_role(void) const; t_subscription_state get_state(void) const; string get_reason_termination(void) const; unsigned long get_resubscribe_after(void) const; bool get_may_resubscribe(void) const; string get_event_type(void) const; string get_event_id(void) const; unsigned long get_expiry(void) const; //@} /** @name Receive requests */ //@{ /** * Reveive SUBSCRIBE request * @param r [in] Received request. * @param tuid [in] Transaction user id. * @param tid [in] Transaction id. * @return The return value indicates if processing is finished. * This way a subclass can first call the parent class method. * If the parent indicates that process is finished, then the child * does not need to further process. * Note that recv_subscribe returns false if the SUBSCRIBE is valid. The * subscription timer will be started, but no response is sent. The subclass * MUST further handle the SUBSCRIBE, i.e. send a response and a NOTIFY. */ virtual bool recv_subscribe(t_request *r, t_tuid tuid, t_tid tid); /** * Receive NOTIFY request. * @param r [in] Received request. * @param tuid [in] Transaction user id. * @param tid [in] Transaction id. * @return When the NOTIFY is valid, false is returned. The subclass MUST further * handle the NOTIFY, i.e. send a response. */ virtual bool recv_notify(t_request *r, t_tuid tuid, t_tid tid); //@} /** @name Receive responses */ //@{ /** * Receive NOTIFY/SUBSCRIBE response. * @param r [in] Received response. * @param tuid [in] Transaction user id. * @param tid [in] Transaction id. * @return The return value indicates if processing is finished. */ virtual bool recv_response(t_response *r, t_tuid tuid, t_tid tid); /** * Receive NOTIFY response. * @param r [in] Received response. * @param tuid [in] Transaction user id. * @param tid [in] Transaction id. * @return The return value indicates if processing is finished. */ virtual bool recv_notify_response(t_response *r, t_tuid tuid, t_tid tid); /** * Receive SUBSCRIBE response. * @param r [in] Received response. * @param tuid [in] Transaction user id. * @param tid [in] Transaction id. * @return The return value indicates if processing is finished. */ virtual bool recv_subscribe_response(t_response *r, t_tuid tuid, t_tid tid); //@} /** * Process timeouts * @param timer [in] Type of subscription timer. * @return The return value indicates if processing is finished. */ virtual bool timeout(t_subscribe_timer timer); /** * Match timer id with a running timer. * @param timer [in] Type of subscription timer. * @return True, if id matches, otherwise false. */ virtual bool match_timer(t_subscribe_timer timer, t_object_id id_timer) const; /** * Does incoming request match with event type and id? * @param r [in] Request to match. * @return True if request matches, otherwise false. */ virtual bool match(t_request *r) const; /** * Check if subscription is pending. * @return True if subscription is pending, otherwise false. */ bool is_pending(void) const; /** * Subscribe to an event. * @param expires [in] Expiry in seconds. If expires == 0, then the default duration is used. */ virtual void subscribe(unsigned long expires); /** Unsubscribe from an event. */ virtual void unsubscribe(void); /** Refresh subscription. */ virtual void refresh_subscribe(void); }; #endif twinkle-1.10.1/src/subscription_dialog.cpp000066400000000000000000000254421277565361200206320ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "subscription_dialog.h" #include #include "log.h" #include "phone.h" #include "phone_user.h" #include "protocol.h" #include "userintf.h" #include "util.h" #include "audits/memman.h" extern t_phone *phone; extern string local_hostname; t_subscription_dialog::t_subscription_dialog(t_phone_user *_phone_user) : t_abstract_dialog(_phone_user), subscription(NULL) {} void t_subscription_dialog::send_request(t_request *r, t_tuid tuid) { t_user *user_config = phone_user->get_user_profile(); phone->send_request(user_config, r, tuid); } void t_subscription_dialog::process_subscribe(t_request *r, t_tuid tuid, t_tid tid) { if (get_subscription_state() == SS_NULL) { // Process initial incoming SUBSCRIBE. Create dialog state. // Set local tag if (r->hdr_to.tag.size() == 0) { local_tag = NEW_TAG; } else { local_tag = r->hdr_to.tag; } call_id = r->hdr_call_id.call_id; // Initialize local seqnr local_seqnr = NEW_SEQNR; local_resp_nr = NEW_SEQNR; remote_tag = r->hdr_from.tag; local_uri = r->hdr_to.uri; local_display = r->hdr_to.display; remote_uri = r->hdr_from.uri; remote_display = r->hdr_from.display; // Set remote target URI and display name remote_target_uri = r->hdr_contact.contact_list.front().uri; remote_target_display = r-> hdr_contact.contact_list.front().display; // Set route set if (r->hdr_record_route.is_populated()) { route_set = r->hdr_record_route.route_list; } } (void)subscription->recv_subscribe(r, tuid, tid); } void t_subscription_dialog::process_notify(t_request *r, t_tuid tuid, t_tid tid) { // RFC 3265 3.1.4.4 // A NOTIFY may be received before a 2XX response on the SUBSCRIBE. // If this happens, the remote information must be set using the // NOTIFY from header. if (remote_tag.empty()) { remote_tag = r->hdr_from.tag; remote_uri = r->hdr_from.uri; remote_display = r->hdr_from.display; } (void)subscription->recv_notify(r, tuid, tid); } bool t_subscription_dialog::process_initial_subscribe_response(t_response *r, t_tuid tuid, t_tid tid) { switch (r->get_class()) { case R_2XX: remote_tag = r->hdr_to.tag; remote_uri = r->hdr_to.uri; remote_display = r->hdr_to.display; create_route_set(r); create_remote_target(r); break; case R_4XX: if (r->code == R_423_INTERVAL_TOO_BRIEF) { if (!r->hdr_min_expires.is_populated()) { // Violation of RFC 3261 10.3 item 7 log_file->write_report("Expires header missing from 423 response.", "t_subscription_dialog::process_initial_subscribe_response", LOG_NORMAL, LOG_WARNING); break; } if (r->hdr_min_expires.time <= subscription->get_expiry()) { // Wrong Min-Expires time string s = "Min-Expires ("; s += ulong2str(r->hdr_min_expires.time); s += ") is smaller than the requested "; s += "time ("; s += ulong2str(subscription->get_expiry()); s += ")"; log_file->write_report(s, "t_subscription_dialog::process_initial_subscribe_response", LOG_NORMAL, LOG_WARNING); break; } // Subscribe with the advised interval subscribe(r->hdr_min_expires.time, remote_target_uri, remote_uri, remote_display); return true; } break; default: break; } return false; } t_subscription_dialog::~t_subscription_dialog() { if (subscription) { MEMMAN_DELETE(subscription); delete subscription; } } t_request *t_subscription_dialog::create_request(t_method m) { t_user *user_config = phone_user->get_user_profile(); t_request *r = t_abstract_dialog::create_request(m); // Contact header t_contact_param contact; switch (m) { case REFER: case SUBSCRIBE: case NOTIFY: // RFC 3265 7.1, RFC 3515 2.2 // Contact header is mandatory contact.uri.set_url(user_config->create_user_contact(false, h_ip2str(r->get_local_ip()))); r->hdr_contact.add_contact(contact); break; default: break; } return r; } bool t_subscription_dialog::resend_request_auth(t_response *resp) { t_client_request **current_cr = &(subscription->req_out); if (!*current_cr) return false; t_request *req = (*current_cr)->get_request(); if (phone_user->authorize(req, resp)) { resend_request(*current_cr); return true; } return false; } bool t_subscription_dialog::redirect_request(t_response *resp) { t_user *user_config = phone_user->get_user_profile(); t_client_request **current_cr = &(subscription->req_out); if (!*current_cr) return false; t_request *req = (*current_cr)->get_request(); // If the response is a 3XX response then add redirection // contacts if (resp->get_class() == R_3XX && resp->hdr_contact.is_populated()) { (*current_cr)->redirector.add_contacts( resp->hdr_contact.contact_list); } // Get next destination t_contact_param contact; if ((*current_cr)->redirector.get_next_contact(contact)) { // Ask user for permission to redirect if indicated // by user config bool permission = true; if (user_config->get_ask_user_to_redirect()) { permission = ui->cb_ask_user_to_redirect_request( user_config, contact.uri, contact.display, resp->hdr_cseq.method); } if (permission) { req->uri = contact.uri; req->calc_destinations(*user_config); ui->cb_redirecting_request(user_config, contact); resend_request(*current_cr); return true; } } return false; } bool t_subscription_dialog::failover_request(t_response *resp) { t_client_request **current_cr = &(subscription->req_out); if (!*current_cr) return false; t_request *req = (*current_cr)->get_request(); if (req->next_destination()) { log_file->write_report("Failover to next destination.", "t_subscription_dialog::handle_response_out_of_dialog"); resend_request(*current_cr); return true; } return false; } void t_subscription_dialog::recvd_response(t_response *r, t_tuid tuid, t_tid tid) { t_user *user_config = phone_user->get_user_profile(); t_abstract_dialog::recvd_response(r, tuid ,tid); t_client_request *cr = subscription->req_out; if (!cr) return; // Check cseq if (r->hdr_cseq.method != cr->get_request()->method) { return; } if (r->hdr_cseq.seqnr != cr->get_request()->hdr_cseq.seqnr) return; // Authentication if (r->must_authenticate()) { if (resend_request_auth(r)) { return; } // Authentication failed // Handle the 401/407 as a normal failure response } // RFC 3263 4.3 // Failover if (r->code == R_503_SERVICE_UNAVAILABLE) { if (failover_request(r)) { return; } } // Redirect failed request if there is another destination if (r->get_class() > R_2XX && user_config->get_allow_redirection()) { if (redirect_request(r)) { return; } } // Set the transaction identifier. This identifier is needed if the // transaction must be aborted at a later time. cr->set_tid(tid); switch (r->hdr_cseq.method) { case SUBSCRIBE: // Process response to initial SUBSCRIBE if (get_subscription_state() == SS_NULL) { if (process_initial_subscribe_response(r, tuid, tid)) { return; } } break; default: break; } (void)subscription->recv_response(r, tuid, tid); } void t_subscription_dialog::recvd_request(t_request *r, t_tuid tuid, t_tid tid) { t_response *resp; t_abstract_dialog::recvd_request(r, tuid, tid); // Check cseq // RFC 3261 12.2.2 if (remote_seqnr_set && r->hdr_cseq.seqnr <= remote_seqnr) { // Request received out of order. log_file->write_header("t_subscription_dialog::recvd_request", LOG_NORMAL, LOG_WARNING); log_file->write_raw("CSeq seqnr is out of sequence.\n"); log_file->write_raw("Reveived seqnr: "); log_file->write_raw(r->hdr_cseq.seqnr); log_file->write_endl(); log_file->write_raw("Remote seqnr: "); log_file->write_raw(remote_seqnr); log_file->write_endl(); log_file->write_footer(); resp = r->create_response(R_500_INTERNAL_SERVER_ERROR, "Request received out of order"); phone->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; return; } remote_seqnr = r->hdr_cseq.seqnr; remote_seqnr_set = true; switch (r->method) { case SUBSCRIBE: process_subscribe(r, tuid, tid); break; case NOTIFY: process_notify(r, tuid, tid); break; default: // Other requests are not supported in a subscription dialog. resp = r->create_response(R_500_INTERNAL_SERVER_ERROR); phone->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; break; } } bool t_subscription_dialog::match_request(t_request *r, bool &partial) { if (!subscription->match(r)) return false; if (t_abstract_dialog::match_request(r)) { return true; } partial = t_abstract_dialog::match_partial_request(r); return false; } t_subscription_state t_subscription_dialog::get_subscription_state(void) const { return subscription->get_state(); } string t_subscription_dialog::get_reason_termination(void) const { return subscription->get_reason_termination(); } unsigned long t_subscription_dialog::get_resubscribe_after(void) const { return subscription->get_resubscribe_after(); } bool t_subscription_dialog::get_may_resubscribe(void) const { return subscription->get_may_resubscribe(); } bool t_subscription_dialog::timeout(t_subscribe_timer timer) { return subscription->timeout(timer); } bool t_subscription_dialog::match_timer(t_subscribe_timer timer, t_object_id id_timer) const { return subscription->match_timer(timer, id_timer); } void t_subscription_dialog::subscribe(unsigned long expires, const t_url &req_uri, const t_url &to_uri, const string &to_display) { t_user *user_config = phone_user->get_user_profile(); assert (get_subscription_state() == SS_NULL); call_id = NEW_CALL_ID(user_config); call_id_owner = true; local_tag = NEW_TAG; local_display = user_config->get_display(false); local_uri = user_config->create_user_uri(false); local_seqnr = rand() % 1000 + 1; remote_uri = to_uri; remote_display = to_display; remote_tag.clear(); remote_target_uri = req_uri; route_set = phone_user->get_service_route(); subscription->subscribe(expires); } void t_subscription_dialog::unsubscribe(void) { subscription->unsubscribe(); } void t_subscription_dialog::refresh_subscribe(void) { subscription->refresh_subscribe(); } twinkle-1.10.1/src/subscription_dialog.h000066400000000000000000000120621277565361200202710ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Subscription dialog (RFC 3265) */ #ifndef _SUBSCRIPTION_DIALOG_H #define _SUBSCRIPTION_DIALOG_H #include "abstract_dialog.h" #include "subscription.h" // Forward declaration class t_phone_user; /** * RFC 3265 * Generic subscription dialog state for subscribers and notifiers. * For each event type this class should be subclassed. */ class t_subscription_dialog : public t_abstract_dialog { protected: /** * The subscription belonging to this dialog. Subclasses must * create the proper subscription. */ t_subscription *subscription; /** * Constructor. This class must be subclassed. The subclass must provide * a public constructor. */ t_subscription_dialog(t_phone_user *_phone_user); virtual void send_request(t_request *r, t_tuid tuid); /** * Process a received SUBSCRIBE request. * @param r [in] The request. * @param tuid [in] Transaction user id. * @param tid [in] Transaction id. */ virtual void process_subscribe(t_request *r, t_tuid tuid, t_tid tid); /** * Process a received NOTIFY request. * @param r [in] The request. * @param tuid [in] Transaction user id. * @param tid [in] Transaction id. */ virtual void process_notify(t_request *r, t_tuid tuid, t_tid tid); /** * Process the response to the initial SUBSCRIBE. * @param r [in] The response. * @param tuid [in] Transaction user id. * @param tid [in] Transaction id. * @return true, if no further processing is needed. This happens, when a * 423 Interval too brief response is received. Then this method sends a * new SUBSCRIBE. * @return false, subcalss must do further processing. */ virtual bool process_initial_subscribe_response(t_response *r, t_tuid tuid, t_tid tid); public: /** Destructor. */ virtual ~t_subscription_dialog(); virtual t_request *create_request(t_method m); virtual t_subscription_dialog *copy(void) = 0; virtual bool resend_request_auth(t_response *resp); virtual bool redirect_request(t_response *resp); virtual bool failover_request(t_response *resp); virtual void recvd_response(t_response *r, t_tuid tuid, t_tid tid); virtual void recvd_request(t_request *r, t_tuid tuid, t_tid tid); /** * Match request with dialog and subscription. * @param r [in] The request. * @param partial [out] Indicates if there is a partial match on return. * @return true, if the request matches. * @return false, if the request does not match. In this case the request * may match partially, i.e. the from-tag matches, but the to-tag does not. * In case of a partial match, partial is set to true. */ virtual bool match_request(t_request *r, bool &partial); /** * Get the state of the subscription. * @return The subscription state. */ t_subscription_state get_subscription_state(void) const; /** * Get the reason for termination of the subscription. * @return The termination reason. */ string get_reason_termination(void) const; /** * Get the time after which a resubscription may be tried. * @return The time in seconds. */ unsigned long get_resubscribe_after(void) const; /** * Check if a resubscription may be tried. * @return true, if a resubscription may be tried. * @return false, otherwise. */ bool get_may_resubscribe(void) const; /** * Process timeout. * @param timer [in] The timer that expired. * @return true, if processing is finished. * @return false, if subsclass needs to do further processing. */ virtual bool timeout(t_subscribe_timer timer); /** * Match a timer id with a running timer. * @param timer [in] The running timer. * @param id_timer [in] The timer id. * @return true, if timer id matches with timer. * @return false, otherwise. */ virtual bool match_timer(t_subscribe_timer timer, t_object_id id_timer) const; /** * Subscribe to an event (send SUBSCRIBE). * @param epxires [in] The subscription interval in seconds. * @param req_uri [in] The request-URI for the SUBSCRIBE. * @param to_uri [in] The URI for the To header in the SUBSCRIBE. * @param to_display [in] The display name for the To header in the SUBSCRIBE. */ virtual void subscribe(unsigned long expires, const t_url &req_uri, const t_url &to_uri, const string &to_display); /** Unsubscribe to an event (send SUBSCRIBE). */ virtual void unsubscribe(void); /** Refresh subscription. */ virtual void refresh_subscribe(void); }; #endif twinkle-1.10.1/src/sys_settings.cpp000066400000000000000000001504101277565361200173170ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "twinkle_config.h" #include #include #include #include #include #include #include #include #include #include "sys_settings.h" #include "log.h" #include "translator.h" #include "user.h" #include "userintf.h" #include "util.h" #include "audits/memman.h" #include "utils/file_utils.h" using namespace utils; // Share directory containing files applicable to all users #define DIR_SHARE DATADIR // Lock file to guarantee that a user is running the application only once #define LOCK_FILENAME "twinkle.lck" // System config file #define SYS_CONFIG_FILE "twinkle.sys" // Default location of the shared mime database #define DFLT_SHARED_MIME_DB "/usr/share/mime/globs" // Field names in the config file // AUDIO fields #define FLD_DEV_RINGTONE "dev_ringtone" #define FLD_DEV_SPEAKER "dev_speaker" #define FLD_DEV_MIC "dev_mic" #define FLD_VALIDATE_AUDIO_DEV "validate_audio_dev" #define FLD_ALSA_PLAY_PERIOD_SIZE "alsa_play_period_size" #define FLD_ALSA_CAPTURE_PERIOD_SIZE "alsa_capture_period_size" #define FLD_OSS_FRAGMENT_SIZE "oss_fragment_size" // LOG fields #define FLD_LOG_MAX_SIZE "log_max_size" #define FLD_LOG_SHOW_SIP "log_show_sip" #define FLD_LOG_SHOW_STUN "log_show_stun" #define FLD_LOG_SHOW_MEMORY "log_show_memory" #define FLD_LOG_SHOW_DEBUG "log_show_debug" // GUI settings #define FLD_GUI_USE_SYSTRAY "gui_use_systray" #define FLD_GUI_HIDE_ON_CLOSE "gui_hide_on_close" #define FLD_GUI_AUTO_SHOW_INCOMING "gui_auto_show_incoming" #define FLD_GUI_AUTO_SHOW_TIMEOUT "gui_auto_show_timeout" #define FLD_GUI_BROWSER_CMD "gui_browser_cmd" #define FLD_GUI_SHOW_CALL_OSD "gui_show_call_osd" // Address book settings #define FLD_AB_SHOW_SIP_ONLY "ab_show_sip_only" #define FLD_AB_LOOKUP_NAME "ab_lookup_name" #define FLD_AB_OVERRIDE_DISPLAY "ab_override_display" #define FLD_AB_LOOKUP_PHOTO "ab_lookup_photo" // Call history fields #define FLD_CH_MAX_SIZE "ch_max_size" // Service settings #define FLD_CALL_WAITING "call_waiting" #define FLD_HANGUP_BOTH_3WAY "hangup_both_3way" // Startup settings #define FLD_START_USER_PROFILE "start_user_profile" #define FLD_START_HIDDEN "start_hidden" // Network settings #define FLD_sip_udp_port "sip_udp_port" #define FLD_sip_port "sip_port" #define FLD_RTP_PORT "rtp_port" #define FLD_SIP_MAX_UDP_SIZE "sip_max_udp_size" #define FLD_SIP_MAX_TCP_SIZE "sip_max_tcp_size" // Ring tone settings #define FLD_PLAY_RINGTONE "play_ringtone" #define FLD_RINGTONE_FILE "ringtone_file" #define FLD_PLAY_RINGBACK "play_ringback" #define FLD_RINGBACK_FILE "ringback_file" // Persistent storage for user interface state #define FLD_LAST_USED_PROFILE "last_used_profile" #define FLD_REDIAL_URL "redial_url" #define FLD_REDIAL_DISPLAY "redial_display" #define FLD_REDIAL_SUBJECT "redial_subject" #define FLD_REDIAL_PROFILE "redial_profile" #define FLD_REDIAL_HIDE_USER "redial_hide_user" #define FLD_DIAL_HISTORY "dial_history" #define FLD_SHOW_DISPLAY "show_display" #define FLD_COMPACT_LINE_STATUS "compact_line_status" #define FLD_SHOW_BUDDY_LIST "show_buddy_list" #define FLD_WARN_HIDE_USER "warn_hide_user" // Settings to restore session after shutdown #define FLD_UI_SESSION_ID "ui_session_id" #define FLD_UI_SESSION_ACTIVE_PROFILE "ui_session_active_profile" #define FLD_UI_SESSION_MAIN_GEOMETRY "ui_session_main_geometry" #define FLD_UI_SESSION_MAIN_HIDDEN "ui_session_main_hidden" #define FLD_UI_SESSION_MAIN_STATE "ui_session_main_state" // Mime settings #define FLD_MIME_SHARED_DATABASE "mime_shared_database" ///////////////////////// // class t_audio_device ///////////////////////// string t_audio_device::get_description(void) const { string s = device; if (type == OSS) { s = "OSS: " + s; if (sym_link.size() > 0) { s += " -> "; s += sym_link; } if (name.size() > 0) { s += ": "; s += name; } } else if (type == ALSA) { s = "ALSA: " + s; if (!name.empty()) { s += ": "; s += name; } } else { s = "Unknown: " + s; } return s; } string t_audio_device::get_settings_value(void) const { string s; switch (type) { case OSS: s = PFX_OSS; break; case ALSA: s = PFX_ALSA; break; default: assert(false); } s += device; return s; } ///////////////////////// // class t_win_geometry ///////////////////////// t_win_geometry::t_win_geometry(int x_, int y_, int width_, int height_) : x(x_), y(y_), width(width_), height(height_) {} t_win_geometry::t_win_geometry() : x(0), y(0), width(0), height(0) {} t_win_geometry::t_win_geometry(const string &value) { vector v = split(value, ','); if (v.size() == 4) { x = std::stoi(v[0]); y = std::stoi(v[1]); width = std::stoi(v[2]); height = std::stoi(v[3]); } } string t_win_geometry::encode(void) const { string s; s = int2str(x); s += ','; s += int2str(y); s += ','; s += int2str(width); s += ','; s += int2str(height); return s; } ///////////////////////// // class t_sys_settings ///////////////////////// t_sys_settings::t_sys_settings() { fd_lock_file = -1; dir_share = DIR_SHARE; filename = string(DIR_HOME); filename += "/"; filename += USER_DIR; filename += "/"; filename += SYS_CONFIG_FILE; // Audio device default settings #ifdef HAVE_LIBASOUND dev_ringtone = audio_device(DEV_ALSA_DFLT); dev_speaker = audio_device(DEV_ALSA_DFLT); dev_mic = audio_device(DEV_ALSA_DFLT); #else dev_ringtone = audio_device(); dev_speaker = audio_device(); dev_mic = audio_device(); #endif validate_audio_dev = true; alsa_play_period_size = 128; alsa_capture_period_size = 32; oss_fragment_size = 128; log_max_size = 5; log_show_sip = true; log_show_stun = true; log_show_memory = true; log_show_debug = false; gui_use_systray = true; gui_hide_on_close = true; gui_auto_show_incoming = false; gui_auto_show_timeout = 10; gui_show_call_osd = true; ab_show_sip_only = false; ab_lookup_name = true; ab_override_display = true; ab_lookup_photo = true; ch_max_size = 50; call_waiting = true; hangup_both_3way = true; start_user_profiles.clear(); start_hidden = false; config_sip_port = 5060; active_sip_port = 0; override_sip_port = 0; rtp_port = 8000; override_rtp_port = 0; sip_max_udp_size = 65535; sip_max_tcp_size = 1000000; play_ringtone = true; ringtone_file.clear(); play_ringback = true; ringback_file.clear(); last_used_profile.clear(); redial_url.set_url(""); redial_display.clear(); redial_subject.clear(); redial_profile.clear(); redial_hide_user = false; dial_history.clear(); show_display = true; compact_line_status = false; show_buddy_list = true; warn_hide_user = true; ui_session_id.clear(); mime_shared_database = DFLT_SHARED_MIME_DB; } // Getters t_audio_device t_sys_settings::get_dev_ringtone(void) const { t_audio_device result; mtx_sys.lock(); result = dev_ringtone; mtx_sys.unlock(); return result; } t_audio_device t_sys_settings::get_dev_speaker(void) const { t_audio_device result; mtx_sys.lock(); result = dev_speaker; mtx_sys.unlock(); return result; } t_audio_device t_sys_settings::get_dev_mic(void) const { t_audio_device result; mtx_sys.lock(); result = dev_mic; mtx_sys.unlock(); return result; } bool t_sys_settings::get_validate_audio_dev(void) const { bool result; mtx_sys.lock(); result = validate_audio_dev; mtx_sys.unlock(); return result; } int t_sys_settings::get_alsa_play_period_size(void) const { int result; mtx_sys.lock(); result = alsa_play_period_size; mtx_sys.unlock(); return result; } int t_sys_settings::get_alsa_capture_period_size(void) const { int result; mtx_sys.lock(); result = alsa_capture_period_size; mtx_sys.unlock(); return result; } int t_sys_settings::get_oss_fragment_size(void) const { int result; mtx_sys.lock(); result = oss_fragment_size; mtx_sys.unlock(); return result; } unsigned short t_sys_settings::get_log_max_size(void) const { unsigned short result; mtx_sys.lock(); result = log_max_size; mtx_sys.unlock(); return result; } bool t_sys_settings::get_log_show_sip(void) const { bool result; mtx_sys.lock(); result = log_show_sip; mtx_sys.unlock(); return result; } bool t_sys_settings::get_log_show_stun(void) const { bool result; mtx_sys.lock(); result = log_show_stun; mtx_sys.unlock(); return result; } bool t_sys_settings::get_log_show_memory(void) const { bool result; mtx_sys.lock(); result = log_show_memory; mtx_sys.unlock(); return result; } bool t_sys_settings::get_log_show_debug(void) const { bool result; mtx_sys.lock(); result = log_show_debug; mtx_sys.unlock(); return result; } bool t_sys_settings::get_gui_use_systray(void) const { t_mutex_guard guard(mtx_sys); return gui_use_systray; } bool t_sys_settings::get_gui_auto_show_incoming(void) const { t_mutex_guard guard(mtx_sys); return gui_auto_show_incoming; } int t_sys_settings::get_gui_auto_show_timeout(void) const { t_mutex_guard guard(mtx_sys); return gui_auto_show_timeout; } bool t_sys_settings::get_gui_hide_on_close(void) const { t_mutex_guard guard(mtx_sys); return gui_hide_on_close; } string t_sys_settings::get_gui_browser_cmd(void) const { t_mutex_guard guard(mtx_sys); return gui_browser_cmd; } bool t_sys_settings::get_ab_show_sip_only(void) const { bool result; mtx_sys.lock(); result = ab_show_sip_only; mtx_sys.unlock(); return result; } bool t_sys_settings::get_ab_lookup_name(void) const { bool result; mtx_sys.lock(); result = ab_lookup_name; mtx_sys.unlock(); return result; } bool t_sys_settings::get_ab_override_display(void) const { bool result; mtx_sys.lock(); result = ab_override_display; mtx_sys.unlock(); return result; } bool t_sys_settings::get_ab_lookup_photo(void) const { bool result; mtx_sys.lock(); result = ab_lookup_photo; mtx_sys.unlock(); return result; } int t_sys_settings::get_ch_max_size(void) const { int result; mtx_sys.lock(); result = ch_max_size; mtx_sys.unlock(); return result; } bool t_sys_settings::get_call_waiting(void) const { bool result; mtx_sys.lock(); result = call_waiting; mtx_sys.unlock(); return result; } bool t_sys_settings::get_hangup_both_3way(void) const { bool result; mtx_sys.lock(); result = hangup_both_3way; mtx_sys.unlock(); return result; } list t_sys_settings::get_start_user_profiles(void) const { list result; mtx_sys.lock(); result = start_user_profiles; mtx_sys.unlock(); return result; } bool t_sys_settings::get_start_hidden(void) const { bool result; mtx_sys.lock(); result = start_hidden; mtx_sys.unlock(); return result; } unsigned short t_sys_settings::get_config_sip_port(void) const { unsigned short result; mtx_sys.lock(); result = config_sip_port; mtx_sys.unlock(); return result; } unsigned short t_sys_settings::get_rtp_port(void) const { unsigned short result; mtx_sys.lock(); if (override_rtp_port > 0) { result = override_rtp_port; } else { result = rtp_port; } mtx_sys.unlock(); return result; } unsigned short t_sys_settings::get_sip_max_udp_size(void) const { t_mutex_guard guard(mtx_sys); return sip_max_udp_size; } unsigned long t_sys_settings::get_sip_max_tcp_size(void) const { t_mutex_guard guard(mtx_sys); return sip_max_tcp_size; } bool t_sys_settings::get_play_ringtone(void) const { bool result; mtx_sys.lock(); result = play_ringtone; mtx_sys.unlock(); return result; } string t_sys_settings::get_ringtone_file(void) const { string result; mtx_sys.lock(); result = ringtone_file; mtx_sys.unlock(); return result; } bool t_sys_settings::get_play_ringback(void) const { bool result; mtx_sys.lock(); result = play_ringback; mtx_sys.unlock(); return result; } string t_sys_settings::get_ringback_file(void) const { string result; mtx_sys.lock(); result = ringback_file; mtx_sys.unlock(); return result; } string t_sys_settings::get_last_used_profile(void) const { string result; mtx_sys.lock(); result = last_used_profile; mtx_sys.unlock(); return result; } t_url t_sys_settings::get_redial_url(void) const { t_url result; mtx_sys.lock(); result = redial_url; mtx_sys.unlock(); return result; } string t_sys_settings::get_redial_display(void) const { string result; mtx_sys.lock(); result = redial_display; mtx_sys.unlock(); return result; } string t_sys_settings::get_redial_subject(void) const { string result; mtx_sys.lock(); result = redial_subject; mtx_sys.unlock(); return result; } string t_sys_settings::get_redial_profile(void) const { string result; mtx_sys.lock(); result = redial_profile; mtx_sys.unlock(); return result; } bool t_sys_settings::get_redial_hide_user(void) const { bool result; mtx_sys.lock(); result = redial_hide_user; mtx_sys.unlock(); return result; } list t_sys_settings::get_dial_history(void) const { list result; mtx_sys.lock(); result = dial_history; mtx_sys.unlock(); return result; } bool t_sys_settings::get_show_display(void) const { bool result; mtx_sys.lock(); result = show_display; mtx_sys.unlock(); return result; } bool t_sys_settings::get_compact_line_status(void) const { bool result; mtx_sys.lock(); result = compact_line_status; mtx_sys.unlock(); return result; } bool t_sys_settings::get_show_buddy_list(void) const { bool result; mtx_sys.lock(); result = show_buddy_list; mtx_sys.unlock(); return result; } bool t_sys_settings::get_gui_show_call_osd() const { return gui_show_call_osd; } string t_sys_settings::get_ui_session_id(void) const { t_mutex_guard guard(mtx_sys); return ui_session_id; } list t_sys_settings::get_ui_session_active_profiles(void) const { t_mutex_guard guard(mtx_sys); return ui_session_active_profiles; } t_win_geometry t_sys_settings::get_ui_session_main_geometry(void) const { t_mutex_guard guard(mtx_sys); return ui_session_main_geometry; } bool t_sys_settings::get_ui_session_main_hidden(void) const { t_mutex_guard guard(mtx_sys); return ui_session_main_hidden; } unsigned int t_sys_settings::get_ui_session_main_state(void) const { t_mutex_guard guard(mtx_sys); return ui_session_main_state; } bool t_sys_settings::get_warn_hide_user(void) const { t_mutex_guard guard(mtx_sys); return warn_hide_user; } string t_sys_settings::get_mime_shared_database(void) const { t_mutex_guard guard(mtx_sys); return mime_shared_database; } // Setters void t_sys_settings::set_dev_ringtone(const t_audio_device &dev) { mtx_sys.lock(); dev_ringtone = dev; mtx_sys.unlock(); } void t_sys_settings::set_dev_speaker(const t_audio_device &dev) { mtx_sys.lock(); dev_speaker = dev; mtx_sys.unlock(); } void t_sys_settings::set_dev_mic(const t_audio_device &dev) { mtx_sys.lock(); dev_mic = dev; mtx_sys.unlock(); } void t_sys_settings::set_validate_audio_dev(bool b) { mtx_sys.lock(); validate_audio_dev = b; mtx_sys.unlock(); } void t_sys_settings::set_alsa_play_period_size(int size) { mtx_sys.lock(); alsa_play_period_size = size; mtx_sys.unlock(); } void t_sys_settings::set_alsa_capture_period_size(int size) { mtx_sys.lock(); alsa_capture_period_size = size; mtx_sys.unlock(); } void t_sys_settings::set_oss_fragment_size(int size) { mtx_sys.lock(); oss_fragment_size = size; mtx_sys.unlock(); } void t_sys_settings::set_log_max_size(unsigned short size) { mtx_sys.lock(); log_max_size = size; mtx_sys.unlock(); } void t_sys_settings::set_log_show_sip(bool b) { mtx_sys.lock(); log_show_sip = b; mtx_sys.unlock(); } void t_sys_settings::set_log_show_stun(bool b) { mtx_sys.lock(); log_show_stun = b; mtx_sys.unlock(); } void t_sys_settings::set_log_show_memory(bool b) { t_mutex_guard guard(mtx_sys); log_show_memory = b; } void t_sys_settings::set_log_show_debug(bool b) { t_mutex_guard guard(mtx_sys); log_show_debug = b; } void t_sys_settings::set_gui_use_systray(bool b) { t_mutex_guard guard(mtx_sys); gui_use_systray = b; } void t_sys_settings::set_gui_hide_on_close(bool b) { t_mutex_guard guard(mtx_sys); gui_hide_on_close = b; } void t_sys_settings::set_gui_auto_show_incoming(bool b) { t_mutex_guard guard(mtx_sys); gui_auto_show_incoming = b; } void t_sys_settings::set_gui_auto_show_timeout(int timeout) { t_mutex_guard guard(mtx_sys); gui_auto_show_timeout = timeout; } void t_sys_settings::set_gui_browser_cmd(const string &s) { t_mutex_guard guard(mtx_sys); gui_browser_cmd = s; } void t_sys_settings::set_ab_show_sip_only(bool b) { mtx_sys.lock(); ab_show_sip_only = b; mtx_sys.unlock(); } void t_sys_settings::set_ab_lookup_name(bool b) { mtx_sys.lock(); ab_lookup_name = b; mtx_sys.unlock(); } void t_sys_settings::set_ab_override_display(bool b) { mtx_sys.lock(); ab_override_display = b; mtx_sys.unlock(); } void t_sys_settings::set_ab_lookup_photo(bool b) { mtx_sys.lock(); ab_lookup_photo = b; mtx_sys.unlock(); } void t_sys_settings::set_ch_max_size(int size) { mtx_sys.lock(); ch_max_size = size; mtx_sys.unlock(); } void t_sys_settings::set_call_waiting(bool b) { mtx_sys.lock(); call_waiting = b; mtx_sys.unlock(); } void t_sys_settings::set_hangup_both_3way(bool b) { mtx_sys.lock(); hangup_both_3way = b; mtx_sys.unlock(); } void t_sys_settings::set_start_user_profiles(const list &profiles) { mtx_sys.lock(); start_user_profiles = profiles; mtx_sys.unlock(); } void t_sys_settings::set_start_hidden(bool b) { mtx_sys.lock(); start_hidden = b; mtx_sys.unlock(); } void t_sys_settings::set_config_sip_port(unsigned short port) { mtx_sys.lock(); config_sip_port = port; mtx_sys.unlock(); } void t_sys_settings::set_override_sip_port(unsigned short port) { mtx_sys.lock(); override_sip_port = port; mtx_sys.unlock(); } void t_sys_settings::set_rtp_port(unsigned short port) { mtx_sys.lock(); rtp_port = port; mtx_sys.unlock(); } void t_sys_settings::set_override_rtp_port(unsigned short port) { mtx_sys.lock(); override_rtp_port = port; mtx_sys.unlock(); } void t_sys_settings::set_sip_max_udp_size(unsigned short size) { t_mutex_guard guard(mtx_sys); sip_max_udp_size = size; } void t_sys_settings::set_sip_max_tcp_size(unsigned long size) { t_mutex_guard guard(mtx_sys); sip_max_tcp_size = size; } void t_sys_settings::set_play_ringtone(bool b) { mtx_sys.lock(); play_ringtone = b; mtx_sys.unlock(); } void t_sys_settings::set_ringtone_file(const string &file) { mtx_sys.lock(); ringtone_file = file; mtx_sys.unlock(); } void t_sys_settings::set_play_ringback(bool b) { mtx_sys.lock(); play_ringback = b; mtx_sys.unlock(); } void t_sys_settings::set_ringback_file(const string &file) { mtx_sys.lock(); ringback_file = file; mtx_sys.unlock(); } void t_sys_settings::set_last_used_profile(const string &profile) { mtx_sys.lock(); last_used_profile = profile; mtx_sys.unlock(); } void t_sys_settings::set_redial_url(const t_url &url) { mtx_sys.lock(); redial_url = url; mtx_sys.unlock(); } void t_sys_settings::set_redial_display(const string &display) { mtx_sys.lock(); redial_display = display; mtx_sys.unlock(); } void t_sys_settings::set_redial_subject(const string &subject) { mtx_sys.lock(); redial_subject = subject; mtx_sys.unlock(); } void t_sys_settings::set_redial_profile(const string &profile) { mtx_sys.lock(); redial_profile = profile; mtx_sys.unlock(); } void t_sys_settings::set_redial_hide_user(const bool b) { mtx_sys.lock(); redial_hide_user = b; mtx_sys.unlock(); } void t_sys_settings::set_dial_history(const list &history) { mtx_sys.lock(); dial_history = history; mtx_sys.unlock(); } void t_sys_settings::set_show_display(bool b) { mtx_sys.lock(); show_display = b; mtx_sys.unlock(); } void t_sys_settings::set_compact_line_status(bool b) { mtx_sys.lock(); compact_line_status = b; mtx_sys.unlock(); } void t_sys_settings::set_show_buddy_list(bool b) { mtx_sys.lock(); show_buddy_list = b; mtx_sys.unlock(); } void t_sys_settings::set_ui_session_id(const string &id) { t_mutex_guard guard(mtx_sys); ui_session_id = id; } void t_sys_settings::set_ui_session_active_profiles(const list &profiles) { t_mutex_guard guard(mtx_sys); ui_session_active_profiles = profiles; } void t_sys_settings::set_ui_session_main_geometry(const t_win_geometry &geometry) { t_mutex_guard guard(mtx_sys); ui_session_main_geometry = geometry; } void t_sys_settings::set_ui_session_main_hidden(bool hidden) { t_mutex_guard guard(mtx_sys); ui_session_main_hidden = hidden; } void t_sys_settings::set_ui_session_main_state(unsigned int state) { t_mutex_guard guard(mtx_sys); ui_session_main_state = state; } void t_sys_settings::set_warn_hide_user(bool b) { t_mutex_guard guard(mtx_sys); warn_hide_user = b; } void t_sys_settings::set_gui_show_call_osd(bool b) { // Using mutexes in primitive type getters/setters doesn't make any sense. // TODO: remove t_mutex_guard from other getters/setters like this one. gui_show_call_osd = b; } void t_sys_settings::set_mime_shared_database(const string &filename) { t_mutex_guard guard(mtx_sys); mime_shared_database = filename; } string t_sys_settings::about(bool html) const { string s = PRODUCT_NAME; s += ' '; s += PRODUCT_VERSION; s += " - "; s += get_product_date(); if (html) s += "
"; s += "\n"; s += "Copyright (C) 2005-2015 "; s += PRODUCT_AUTHOR; if (html) s += "
"; s += "\n"; s += "http://twinkle.dolezel.info"; if (html) s += "

"; s += "\n\n"; string options_built = get_options_built(); if (!options_built.empty()) { s += TRANSLATE("Built with support for:"); s += " "; s += options_built; if (html) s += "

"; s += "\n\n"; } s += TRANSLATE("Contributions:"); if (html) s += "
"; s += "\n"; s += "* Werner Dittmann (ZRTP/SRTP)\n"; if (html) s += "
"; s += "* Bogdan Harjoc (AKAv1-MD5, Service-Route)\n"; if (html) s += "
"; s += "* Roman Imankulov (command line editing)\n"; if (html) s += "
"; if (html) { s += "* Ondrej Moriš (codec preprocessing)
\n"; } else { s += "* Ondrej Moris (codec preprocessing)\n"; } if (html) { s += "* Rickard Petzäll (ALSA)
\n"; } else { s += "* Rickard Petzall (ALSA)\n"; } if (html) s += "
"; s += "\n"; s += TRANSLATE("This software contains the following software from 3rd parties:"); if (html) s += "
"; s += "\n"; s += TRANSLATE("* GSM codec from Jutta Degener and Carsten Bormann, University of Berlin"); if (html) s += "
"; s += "\n"; s += TRANSLATE("* G.711/G.726 codecs from Sun Microsystems (public domain)"); if (html) s += "
"; s += "\n"; #ifdef HAVE_ILBC s += TRANSLATE("* iLBC implementation from RFC 3951 (www.ilbcfreeware.org)"); if (html) s += "
"; s += "\n"; #endif #ifdef HAVE_BCG729 s += TRANSLATE("* G729A codec (http://www.linphone.org/technical-corner/bcg729/overview"); if (html) s += "
"; s += "\n"; #endif s += TRANSLATE("* Parts of the STUN project at http://sourceforge.net/projects/stun"); if (html) s += "
"; s += "\n"; s += TRANSLATE("* Parts of libsrv at http://libsrv.sourceforge.net/"); if (html) s += "
"; s += "\n"; if (html) s += "
"; s += "\n"; s += TRANSLATE("For RTP the following dynamic libraries are linked:"); if (html) s += "
"; s += "\n"; s += "* GNU ccRTP - http://www.gnu.org/software/ccrtp"; if (html) s += "
"; s += "\n"; s += "* GNU uCommon C++ - http://www.gnutelephony.org/index.php/Category:Software"; if (html) s += "

"; s += "\n\n"; // Display information about translator only on non-english version. string translated_by = TRANSLATE("Translated to english by "); if (translated_by != "Translated to english by ") { s += translated_by; if (html) s += "

"; s += "\n\n"; } s += PRODUCT_NAME; s += " comes with ABSOLUTELY NO WARRANTY."; if (html) s += "
"; s += "\n"; s += "This program is free software; you can redistribute it and/or modify"; if (html) s += "
"; s += "\n"; s += "it under the terms of the GNU General Public License as published by"; if (html) s += "
"; s += "\n"; s += "the Free Software Foundation; either version 2 of the License, or"; if (html) s += "
"; s += "\n"; s += "(at your option) any later version."; if (html) s += "
"; s += "\n"; return s; } string t_sys_settings::get_product_date(void) const { struct tm t; t.tm_sec = 0; t.tm_min = 0; t.tm_hour = 0; vector l = split(PRODUCT_DATE, ' '); assert(l.size() == 3); t.tm_mon = str2month_full(l[0]); t.tm_mday = std::stoi(l[1]); t.tm_year = std::stoi(l[2]) - 1900; char buf[64]; strftime(buf, 64, "%d %B %Y", &t); return string(buf); } string t_sys_settings::get_options_built(void) const { string options_built; #ifdef HAVE_LIBASOUND if (!options_built.empty()) options_built += ", "; options_built += "ALSA"; #endif #ifdef HAVE_KDE if (!options_built.empty()) options_built += ", "; options_built += "KDE"; #endif #ifdef HAVE_SPEEX if (!options_built.empty()) options_built += ", "; options_built += "Speex"; #endif #ifdef HAVE_ILBC if (!options_built.empty()) options_built += ", "; options_built += "iLBC"; #endif #ifdef HAVE_BCG729 if (!options_built.empty()) options_built += ", "; options_built += "G729"; #endif #ifdef HAVE_ZRTP if (!options_built.empty()) options_built += ", "; options_built += "ZRTP"; #endif return options_built; } bool t_sys_settings::check_environment(string &error_msg) const { struct stat stat_buf; string filename, dirname; mtx_sys.lock(); // Check if share directory exists if (stat(dir_share.c_str(), &stat_buf) != 0) { error_msg = TRANSLATE("Directory %1 does not exist."); error_msg = replace_first(error_msg, "%1", dir_share); mtx_sys.unlock(); return false; } // Check if audio file for ring tone exist filename = dir_share; filename += '/'; filename += FILE_RINGTONE; ifstream f_ringtone(filename.c_str()); if (!f_ringtone) { error_msg = TRANSLATE("Cannot open file %1 ."); error_msg = replace_first(error_msg, "%1", filename); mtx_sys.unlock(); return false; } // Check if audio file for ring back exist filename = dir_share; filename += '/'; filename += FILE_RINGBACK; ifstream f_ringback(filename.c_str()); if (!f_ringback) { error_msg = TRANSLATE("Cannot open file %1 ."); error_msg = replace_first(error_msg, "%1", filename); mtx_sys.unlock(); return false; } // Check if $HOME is set correctly if (string(DIR_HOME) == "") { error_msg = TRANSLATE("%1 is not set to your home directory."); error_msg = replace_first(error_msg, "%1", "$HOME"); mtx_sys.unlock(); return false; } if (stat(DIR_HOME, &stat_buf) != 0) { error_msg = TRANSLATE("Directory %1 (%2) does not exist."); error_msg = replace_first(error_msg, "%1", DIR_HOME); error_msg = replace_first(error_msg, "%2", "$HOME"); mtx_sys.unlock(); return false; } // Check if user directory exists dirname = get_dir_user(); if (stat(dirname.c_str(), &stat_buf) != 0) { // User directory does not exist. Create it now. if (mkdir(dirname.c_str(), S_IRUSR | S_IWUSR | S_IXUSR) != 0) { // Failed to create the user directory error_msg = TRANSLATE("Cannot create directory %1 ."); error_msg = replace_first(error_msg, "%1", dirname); mtx_sys.unlock(); return false; } } // Check if tmp file directory exists dirname = get_dir_tmpfile(); if (stat(dirname.c_str(), &stat_buf) != 0) { // Tmp file directory does not exist. Create it now. if (mkdir(dirname.c_str(), S_IRUSR | S_IWUSR | S_IXUSR) != 0) { // Failed to create the tmp file directory error_msg = TRANSLATE("Cannot create directory %1 ."); error_msg = replace_first(error_msg, "%1", dirname); mtx_sys.unlock(); return false; } } mtx_sys.unlock(); return true; } void t_sys_settings::set_dir_share(const string &dir) { mtx_sys.lock(); dir_share = dir; mtx_sys.unlock(); } string t_sys_settings::get_dir_share(void) const { string result; mtx_sys.lock(); result = dir_share; mtx_sys.unlock(); return result; } string t_sys_settings::get_dir_lang(void) const { string result = get_dir_share(); result += "/lang"; return result; } string t_sys_settings::get_dir_user(void) const { string dir = DIR_HOME; dir += "/"; dir += DIR_USER; return dir; } string t_sys_settings::get_history_file(void) const { string dir = get_dir_user(); dir += "/"; dir += FILE_CLI_HISTORY; return dir; } string t_sys_settings::get_dir_tmpfile(void) const { string dir = get_dir_user(); dir += "/"; dir += DIR_TMPFILE; return dir; } bool t_sys_settings::is_tmpfile(const string &filename) const { string tmpdir = get_dir_tmpfile(); return filename.substr(0, tmpdir.size()) == tmpdir; } bool t_sys_settings::save_tmp_file(const string &data, const string &file_extension, string &filename, string &error_msg) { string fname = get_dir_tmpfile(); fname += "/XXXXXX"; char *tmpfile = strdup(fname.c_str()); MEMMAN_NEW(tmpfile); int fd = mkstemp(tmpfile); if (fd < 0) { error_msg = get_error_str(errno); MEMMAN_DELETE(tmpfile); free(tmpfile); return false; } close(fd); ofstream f(tmpfile); if (!f) { error_msg = TRANSLATE("Failed to create file %1"); error_msg = replace_first(error_msg, "%1", tmpfile); MEMMAN_DELETE(tmpfile); free(tmpfile); return false; } f.write(data.c_str(), data.size()); if (!f.good()) { error_msg = TRANSLATE("Failed to write data to file %1"); error_msg = replace_first(error_msg, "%1", tmpfile); f.close(); MEMMAN_DELETE(tmpfile); free(tmpfile); return false; } f.close(); // Rename to name with extension filename = apply_glob_to_filename(tmpfile, file_extension); if (rename(tmpfile, filename.c_str()) < 0) { error_msg = get_error_str(errno); MEMMAN_DELETE(tmpfile); free(tmpfile); return false; } MEMMAN_DELETE(tmpfile); free(tmpfile); return true; } bool t_sys_settings::save_sip_body(const t_sip_message &sip_msg, const string &suggested_file_extension, string &tmpname, string &save_as_name, string &error_msg) { bool retval = true; if (!sip_msg.body) { error_msg = "Missing body"; return false; } // Determine file extension and save-as name // The algorithm to get the file extension (glob expression) is: // 1) If the a file name is supplied in the Content-Disposition header, then // take the file extension from that file name. // 2) If no extension is found, then take the suggested_file_extension // 3) If still no file extension is found, then retrieve the file extension // from the t_media object in the Content-Type header. string file_ext = suggested_file_extension; save_as_name.clear(); if (sip_msg.hdr_content_disp.is_populated() && sip_msg.hdr_content_disp.type == DISPOSITION_ATTACHMENT && !sip_msg.hdr_content_disp.filename.empty()) { string x = get_extension_from_filename(sip_msg.hdr_content_disp.filename); if (!x.empty()) file_ext = string("*." + x); save_as_name = strip_path_from_filename(sip_msg.hdr_content_disp.filename); } if (file_ext.empty()) { file_ext = sip_msg.hdr_content_type.media.get_file_glob(); if (file_ext.empty()) { file_ext = "*"; } } // Avoid copy of opaque data if (sip_msg.body->get_type() == BODY_OPAQUE) { t_sip_body_opaque *body_opaque = dynamic_cast(sip_msg.body); retval = save_tmp_file(body_opaque->opaque, file_ext, tmpname, error_msg); } else { retval = save_tmp_file(sip_msg.body->encode(), file_ext, tmpname, error_msg); } return retval; } void t_sys_settings::remove_all_tmp_files(void) const { DIR *tmpdir = opendir(get_dir_tmpfile().c_str()); if (!tmpdir) { log_file->write_report(get_error_str(errno), "t_sys_settings::remove_all_tmp_files"); return; } struct dirent *entry = readdir(tmpdir); while (entry) { if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) { string fname = get_dir_tmpfile(); fname += PATH_SEPARATOR; fname += entry->d_name; log_file->write_header("t_sys_settings::remove_all_tmp_files"); log_file->write_raw("Remove tmp file "); log_file->write_raw(fname); log_file->write_endl(); log_file->write_footer(); unlink(fname.c_str()); } entry = readdir(tmpdir); } closedir(tmpdir); } bool t_sys_settings::create_lock_file(bool shared_lock, string &error_msg, bool &already_running) { string lck_filename; already_running = false; lck_filename = DIR_HOME; lck_filename += "/"; lck_filename += DIR_USER; lck_filename += "/"; lck_filename += LOCK_FILENAME; fd_lock_file = open(lck_filename.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); if (fd_lock_file < 0) { error_msg = TRANSLATE("Cannot create %1 ."); error_msg = replace_first(error_msg, "%1", lck_filename); error_msg += "\n"; error_msg += get_error_str(errno); return false; } struct flock lock_options; // Try to acquire an exclusive lock if (!shared_lock) { memset(&lock_options, 0, sizeof(struct flock)); lock_options.l_type = F_WRLCK; lock_options.l_whence = SEEK_SET; if (fcntl(fd_lock_file, F_SETLK, &lock_options) < 0) { already_running = true; error_msg = TRANSLATE("%1 is already running.\nLock file %2 already exists."); error_msg = replace_first(error_msg, "%1", PRODUCT_NAME); error_msg = replace_first(error_msg, "%2", lck_filename); return false; } } // Convert the lock to a shared lock. If the user forces multiple // instances of Twinkle to run, then each will have a shared lock. memset(&lock_options, 0, sizeof(struct flock)); lock_options.l_type = F_RDLCK; lock_options.l_whence = SEEK_SET; if (fcntl(fd_lock_file, F_SETLK, &lock_options) < 0) { error_msg = TRANSLATE("Cannot lock %1 ."); error_msg = replace_first(error_msg, "%1", lck_filename); return false; } return true; } void t_sys_settings::delete_lock_file(void) { if (fd_lock_file >= 0) { struct flock lock_options; lock_options.l_type = F_UNLCK; lock_options.l_whence = SEEK_SET; fcntl(fd_lock_file, F_SETLK, &lock_options); close(fd_lock_file); fd_lock_file = -1; } } bool t_sys_settings::read_config(string &error_msg) { struct stat stat_buf; mtx_sys.lock(); // Check if config file exists if (stat(filename.c_str(), &stat_buf) != 0) { mtx_sys.unlock(); return true; } // Open config file ifstream config(filename.c_str()); if (!config) { error_msg = TRANSLATE("Cannot open file for reading: %1"); error_msg = replace_first(error_msg, "%1", filename); mtx_sys.unlock(); return false; } // Read and parse config file. while (!config.eof()) { string line; getline(config, line); // Check if read operation succeeded if (!config.good() && !config.eof()) { error_msg = TRANSLATE("File system error while reading file %1 ."); error_msg = replace_first(error_msg, "%1", filename); mtx_sys.unlock(); return false; } line = trim(line); // Skip empty lines if (line.size() == 0) continue; // Skip comment lines if (line[0] == '#') continue; vector l = split_on_first(line, '='); if (l.size() != 2) { error_msg = TRANSLATE("Syntax error in file %1 ."); error_msg = replace_first(error_msg, "%1", filename); error_msg += "\n"; error_msg += line; mtx_sys.unlock(); return false; } string parameter = trim(l[0]); string value = trim(l[1]); if (parameter == FLD_DEV_RINGTONE) { dev_ringtone = audio_device(value); } else if (parameter == FLD_DEV_SPEAKER) { dev_speaker = audio_device(value); } else if (parameter == FLD_DEV_MIC) { dev_mic = audio_device(value); } else if (parameter == FLD_VALIDATE_AUDIO_DEV) { validate_audio_dev = yesno2bool(value); } else if (parameter == FLD_ALSA_PLAY_PERIOD_SIZE) { alsa_play_period_size = atoi(value.c_str()); } else if (parameter == FLD_ALSA_CAPTURE_PERIOD_SIZE) { alsa_capture_period_size = atoi(value.c_str()); } else if (parameter == FLD_OSS_FRAGMENT_SIZE) { oss_fragment_size = atoi(value.c_str()); } else if (parameter == FLD_LOG_MAX_SIZE) { log_max_size = atoi(value.c_str()); } else if (parameter == FLD_LOG_SHOW_SIP) { log_show_sip = yesno2bool(value); } else if (parameter == FLD_LOG_SHOW_STUN) { log_show_stun = yesno2bool(value); } else if (parameter == FLD_LOG_SHOW_MEMORY) { log_show_memory = yesno2bool(value); } else if (parameter == FLD_LOG_SHOW_DEBUG) { log_show_debug = yesno2bool(value); } else if (parameter == FLD_GUI_USE_SYSTRAY) { gui_use_systray = yesno2bool(value); } else if (parameter == FLD_GUI_HIDE_ON_CLOSE) { gui_hide_on_close = yesno2bool(value); } else if (parameter == FLD_GUI_AUTO_SHOW_INCOMING) { gui_auto_show_incoming = yesno2bool(value); } else if (parameter == FLD_GUI_AUTO_SHOW_TIMEOUT) { gui_auto_show_timeout = atoi(value.c_str()); } else if (parameter == FLD_GUI_BROWSER_CMD) { gui_browser_cmd = value; } else if (parameter == FLD_GUI_SHOW_CALL_OSD) { gui_show_call_osd = yesno2bool(value); } else if (parameter == FLD_AB_SHOW_SIP_ONLY) { ab_show_sip_only = yesno2bool(value); } else if (parameter == FLD_AB_LOOKUP_NAME) { ab_lookup_name = yesno2bool(value); } else if (parameter == FLD_AB_OVERRIDE_DISPLAY) { ab_override_display = yesno2bool(value); } else if (parameter == FLD_AB_LOOKUP_PHOTO) { ab_lookup_photo = yesno2bool(value); } else if (parameter == FLD_CH_MAX_SIZE) { ch_max_size = atoi(value.c_str()); } else if (parameter == FLD_CALL_WAITING) { call_waiting = yesno2bool(value); } else if (parameter == FLD_HANGUP_BOTH_3WAY) { hangup_both_3way = yesno2bool(value); } else if (parameter == FLD_START_USER_PROFILE) { if (!value.empty()) start_user_profiles.push_back(value); } else if (parameter == FLD_START_HIDDEN) { start_hidden = yesno2bool(value); } else if (parameter == FLD_sip_udp_port) { // Deprecated parameter config_sip_port = atoi(value.c_str()); } else if (parameter == FLD_sip_port) { config_sip_port = atoi(value.c_str()); } else if (parameter == FLD_RTP_PORT) { rtp_port = atoi(value.c_str()); } else if (parameter == FLD_SIP_MAX_UDP_SIZE) { sip_max_udp_size = atoi(value.c_str()); } else if (parameter == FLD_SIP_MAX_TCP_SIZE) { sip_max_tcp_size = atoi(value.c_str()); } else if (parameter == FLD_PLAY_RINGTONE) { play_ringtone = yesno2bool(value); } else if (parameter == FLD_RINGTONE_FILE) { ringtone_file = value; } else if (parameter == FLD_PLAY_RINGBACK) { play_ringback = yesno2bool(value); } else if (parameter == FLD_RINGBACK_FILE) { ringback_file = value; } else if (parameter == FLD_LAST_USED_PROFILE) { last_used_profile = value; } else if (parameter == FLD_REDIAL_URL) { redial_url.set_url(value); if (!redial_url.is_valid()) { redial_url.set_url(""); } } else if (parameter == FLD_REDIAL_DISPLAY) { redial_display = value; } else if (parameter == FLD_REDIAL_SUBJECT) { redial_subject = value; } else if (parameter == FLD_REDIAL_PROFILE) { redial_profile = value; } else if (parameter == FLD_REDIAL_HIDE_USER) { redial_hide_user = yesno2bool(value); } else if (parameter == FLD_DIAL_HISTORY) { dial_history.push_back(value); } else if (parameter == FLD_SHOW_DISPLAY) { show_display = yesno2bool(value); } else if (parameter == FLD_COMPACT_LINE_STATUS) { //compact_line_status = yesno2bool(value); } else if (parameter == FLD_SHOW_BUDDY_LIST) { show_buddy_list = yesno2bool(value); } else if (parameter == FLD_UI_SESSION_ID) { ui_session_id = value; } else if (parameter == FLD_UI_SESSION_ACTIVE_PROFILE) { ui_session_active_profiles.push_back(value); } else if (parameter == FLD_UI_SESSION_MAIN_GEOMETRY) { ui_session_main_geometry = value; } else if (parameter == FLD_UI_SESSION_MAIN_HIDDEN) { ui_session_main_hidden = yesno2bool(value); } else if (parameter == FLD_UI_SESSION_MAIN_STATE) { ui_session_main_state = atoi(value.c_str()); } else if (parameter == FLD_WARN_HIDE_USER) { warn_hide_user = yesno2bool(value); } else if (parameter == FLD_MIME_SHARED_DATABASE) { mime_shared_database = value; } // Unknown field names are skipped. } mtx_sys.unlock(); return true; } bool t_sys_settings::write_config(string &error_msg) { struct stat stat_buf; mtx_sys.lock(); // Make a backup of the file if we are editing an existing file, so // that can be restored when writing fails. string f_backup = filename + '~'; if (stat(filename.c_str(), &stat_buf) == 0) { if (rename(filename.c_str(), f_backup.c_str()) != 0) { string err = get_error_str(errno); error_msg = TRANSLATE("Failed to backup %1 to %2"); error_msg = replace_first(error_msg, "%1", filename); error_msg = replace_first(error_msg, "%2", f_backup); error_msg += "\n"; error_msg += err; mtx_sys.unlock(); return false; } } // Open file ofstream config(filename.c_str()); if (!config) { error_msg = TRANSLATE("Cannot open file for writing: %1"); error_msg = replace_first(error_msg, "%1", filename); mtx_sys.unlock(); return false; } // Write AUDIO settings config << "# AUDIO\n"; config << FLD_DEV_RINGTONE << '=' << dev_ringtone.get_settings_value() << endl; config << FLD_DEV_SPEAKER << '=' << dev_speaker.get_settings_value() << endl; config << FLD_DEV_MIC << '=' << dev_mic.get_settings_value() << endl; config << FLD_VALIDATE_AUDIO_DEV << '=' << bool2yesno(validate_audio_dev) << endl; config << FLD_ALSA_PLAY_PERIOD_SIZE << '=' << alsa_play_period_size << endl; config << FLD_ALSA_CAPTURE_PERIOD_SIZE << '=' << alsa_capture_period_size << endl; config << FLD_OSS_FRAGMENT_SIZE << '=' << oss_fragment_size << endl; config << endl; // Write LOG settings config << "# LOG\n"; config << FLD_LOG_MAX_SIZE << '=' << log_max_size << endl; config << FLD_LOG_SHOW_SIP << '=' << bool2yesno(log_show_sip) << endl; config << FLD_LOG_SHOW_STUN << '=' << bool2yesno(log_show_stun) << endl; config << FLD_LOG_SHOW_MEMORY << '=' << bool2yesno(log_show_memory) << endl; config << FLD_LOG_SHOW_DEBUG << '=' << bool2yesno(log_show_debug) << endl; config << endl; // Write GUI settings config << "# GUI\n"; config << FLD_GUI_USE_SYSTRAY << '=' << bool2yesno(gui_use_systray) << endl; config << FLD_GUI_HIDE_ON_CLOSE << '=' << bool2yesno(gui_hide_on_close) << endl; config << FLD_GUI_AUTO_SHOW_INCOMING << '=' << bool2yesno(gui_auto_show_incoming) << endl; config << FLD_GUI_AUTO_SHOW_TIMEOUT << '=' << gui_auto_show_timeout << endl; config << FLD_GUI_BROWSER_CMD << '=' << gui_browser_cmd << endl; config << FLD_GUI_SHOW_CALL_OSD << '=' << bool2yesno(gui_show_call_osd) << endl; config << endl; // Write address book settings config << "# Address book\n"; config << FLD_AB_SHOW_SIP_ONLY << '=' << bool2yesno(ab_show_sip_only) << endl; config << FLD_AB_LOOKUP_NAME << '=' << bool2yesno(ab_lookup_name) << endl; config << FLD_AB_OVERRIDE_DISPLAY << '=' << bool2yesno(ab_override_display) << endl; config << FLD_AB_LOOKUP_PHOTO << '=' << bool2yesno(ab_lookup_photo) << endl; config << endl; // Write call history settings config << "# Call history\n"; config << FLD_CH_MAX_SIZE << '=' << ch_max_size << endl; config << endl; // Write service settings config << "# Services\n"; config << FLD_CALL_WAITING << '=' << bool2yesno(call_waiting) << endl; config << FLD_HANGUP_BOTH_3WAY << '=' << bool2yesno(hangup_both_3way) << endl; config << endl; // Write startup settings config << "# Startup\n"; for (list::iterator i = start_user_profiles.begin(); i != start_user_profiles.end(); i++) { config << FLD_START_USER_PROFILE << '=' << *i << endl; } config << FLD_START_HIDDEN << '=' << bool2yesno(start_hidden) << endl; config << endl; // Write network settings config << "# Network\n"; config << FLD_sip_port << '=' << config_sip_port << endl; config << FLD_RTP_PORT << '=' << rtp_port << endl; config << FLD_SIP_MAX_UDP_SIZE << '=' << sip_max_udp_size << endl; config << FLD_SIP_MAX_TCP_SIZE << '=' << sip_max_tcp_size << endl; config << endl; // Write ring tone settings config << "# Ring tones\n"; config << FLD_PLAY_RINGTONE << '=' << bool2yesno(play_ringtone) << endl; config << FLD_RINGTONE_FILE << '=' << ringtone_file << endl; config << FLD_PLAY_RINGBACK << '=' << bool2yesno(play_ringback) << endl; config << FLD_RINGBACK_FILE << '=' << ringback_file << endl; config << endl; // Write MIME settings config << "# MIME settings\n"; config << FLD_MIME_SHARED_DATABASE << '=' << mime_shared_database << endl; config << endl; // Write persistent user interface state config << "# Persistent user interface state\n"; config << FLD_LAST_USED_PROFILE << '=' << last_used_profile << endl; config << FLD_REDIAL_URL << '=' << redial_url.encode() << endl; config << FLD_REDIAL_DISPLAY << '=' << redial_display << endl; config << FLD_REDIAL_SUBJECT << '=' << redial_subject << endl; config << FLD_REDIAL_PROFILE << '=' << redial_profile << endl; config << FLD_REDIAL_HIDE_USER << '=' << bool2yesno(redial_hide_user) << endl; config << FLD_SHOW_DISPLAY << '=' << bool2yesno(show_display) << endl; //config << FLD_COMPACT_LINE_STATUS << '=' << bool2yesno(compact_line_status) << endl; config << FLD_SHOW_BUDDY_LIST << '=' << bool2yesno(show_buddy_list) << endl; config << FLD_WARN_HIDE_USER << '=' << bool2yesno(warn_hide_user) << endl; for (list::iterator i = dial_history.begin(); i != dial_history.end(); i++) { config << FLD_DIAL_HISTORY << '=' << *i << endl; } config << endl; // Write session settins config << "# UI session settings\n"; config << FLD_UI_SESSION_ID << '=' << ui_session_id << endl; for (list::iterator i = ui_session_active_profiles.begin(); i != ui_session_active_profiles.end(); i++) { config << FLD_UI_SESSION_ACTIVE_PROFILE << '=' << *i << endl; } config << FLD_UI_SESSION_MAIN_GEOMETRY << '=' << ui_session_main_geometry.encode() << endl; config << FLD_UI_SESSION_MAIN_HIDDEN << '=' << bool2yesno(ui_session_main_hidden) << endl; config << FLD_UI_SESSION_MAIN_STATE << '=' << ui_session_main_state << endl; config << endl; // Check if writing succeeded if (!config.good()) { // Restore backup config.close(); rename(f_backup.c_str(), filename.c_str()); error_msg = TRANSLATE("File system error while writing file %1 ."); error_msg = replace_first(error_msg, "%1", filename); mtx_sys.unlock(); return false; } mtx_sys.unlock(); return true; } list t_sys_settings::get_oss_devices(bool playback) const { struct stat stat_buf; list l; for (int i = -1; i <= 15; i ++) { string dev = "/dev/dsp"; if (i >= 0) dev += int2str(i); t_audio_device oss_dev; oss_dev.type = t_audio_device::OSS; // Check if device exists if (stat(dev.c_str(), &stat_buf) != 0) continue; oss_dev.device = dev; // Get sound card name int fd; if (playback) { fd = open(dev.c_str(), O_WRONLY | O_NONBLOCK); } else { fd = open(dev.c_str(), O_RDONLY | O_NONBLOCK); } if (fd >= 0) { struct mixer_info soundcard_info; if (ioctl(fd, SOUND_MIXER_INFO, &soundcard_info) != -1) { oss_dev.name = ""; oss_dev.name += soundcard_info.name; oss_dev.name += " ("; oss_dev.name += soundcard_info.id; oss_dev.name += ")"; } close(fd); } else { if (errno == EBUSY) { oss_dev.name = TRANSLATE("unknown name (device is busy)"); } else { // Device is not available. continue; } } // Check if the device is a symbolic link char buf[32]; int len_link; if ((len_link = readlink(dev.c_str(), buf, 31)) != -1) { buf[len_link] = 0; oss_dev.sym_link = buf; } oss_dev.type = t_audio_device::OSS; l.push_back(oss_dev); } // If no OSS devices can be found (this should not happen), then // just add /dev/dsp as the default device. if (l.empty()) { t_audio_device oss_dev; oss_dev.device = "/dev/dsp"; oss_dev.type = t_audio_device::OSS; l.push_back(oss_dev); } // Add other device option t_audio_device other_dev; other_dev.device = DEV_OTHER; other_dev.type = t_audio_device::OSS; l.push_back(other_dev); return l; } #ifdef HAVE_LIBASOUND // Defined in audio_device.cpp void alsa_fill_soundcards(list& l, bool playback); list t_sys_settings::get_alsa_devices(bool playback) const { t_audio_device defaultDevice; defaultDevice.device = "default"; defaultDevice.name = TRANSLATE("Default device"); defaultDevice.type = t_audio_device::ALSA; list l; l.push_back(defaultDevice); alsa_fill_soundcards(l, playback); // Add other device option t_audio_device other_dev; other_dev.device = DEV_OTHER; other_dev.type = t_audio_device::ALSA; l.push_back(other_dev); return l; } #endif list t_sys_settings::get_audio_devices(bool playback) const { list d, d0; #ifdef HAVE_LIBASOUND d = get_alsa_devices(playback); #endif d0 = get_oss_devices(playback); d.insert(d.end(), d0.begin(), d0.end()); return d; } bool t_sys_settings::equal_audio_dev(const t_audio_device &dev1, const t_audio_device &dev2) const { if (dev1.type == t_audio_device::OSS) { if (dev2.type != t_audio_device::OSS) return false; if (dev1.device == dev2.device) return true; char symlink1[32], symlink2[32]; int len_link1, len_link2; len_link1 = readlink(dev1.device.c_str(), symlink1, 31); len_link2 = readlink(dev2.device.c_str(), symlink2, 31); if (len_link1 > 0) { symlink1[len_link1] = 0; string symdev1 = "/dev/"; symdev1 += symlink1; if (len_link2 > 0) { symlink2[len_link2] = 0; string symdev2 = "/dev/"; symdev2 += symlink2; return symdev1 == symdev2; } else { return dev2.device == symdev1; } } else { if (len_link2 > 0) { symlink2[len_link2] = 0; string symdev2 = "/dev/"; symdev2 += symlink2; return dev1.device == symdev2; } } } else if (dev1.type == t_audio_device::ALSA) { if (dev2.type != t_audio_device::ALSA) return false; return dev1.device == dev2.device; } return false; } t_audio_device t_sys_settings::audio_device(string device) { t_audio_device d; if (device.empty()) device = DEV_DSP; //This is the default device if (device.substr(0, strlen(PFX_OSS)) == PFX_OSS) { // OSS device d.device = device.substr(strlen(PFX_OSS)); d.type = t_audio_device::OSS; d.name = ""; char symlink[32]; int len_link = readlink(device.c_str(), symlink, 31); if(len_link > 0) { d.sym_link = symlink; } } else if (device.substr(0, strlen(PFX_ALSA)) == PFX_ALSA) { // ALSA device d.device = device.substr(strlen(PFX_ALSA)); d.type = t_audio_device::ALSA; d.name = ""; d.sym_link = ""; } else { // Assume it is an OSS device. Version 0.2.1 and lower // only supported OSS and the value only consisted of // the device name without "oss:" d.device = device; d.type = t_audio_device::OSS; d.name = ""; char symlink[32]; int len_link = readlink(device.c_str(), symlink, 31); if(len_link > 0) { d.sym_link = symlink; } } return d; } bool t_sys_settings::exec_audio_validation(bool ringtone, bool speaker, bool mic, string &error_msg) const { error_msg.clear(); if (!validate_audio_dev) return true; bool valid = true; bool full_duplex = speaker && mic && equal_audio_dev(dev_speaker, dev_mic); if (ringtone && !t_audio_io::validate(dev_ringtone, true, false)) { string msg = TRANSLATE("Cannot access the ring tone device (%1)."); error_msg += replace_first(msg, "%1", dev_ringtone.get_description()); error_msg += "\n"; valid = false; } if (speaker && !t_audio_io::validate(dev_speaker, true, full_duplex)) { string msg = TRANSLATE("Cannot access the speaker (%1)."); error_msg += replace_first(msg, "%1", dev_speaker.get_description()); error_msg += "\n"; valid = false; } if (mic && !t_audio_io::validate(dev_mic, full_duplex, true)) { string msg = TRANSLATE("Cannot access the microphone (%1)."); error_msg += replace_first(msg, "%1", dev_mic.get_description()); error_msg += "\n"; valid = false; } return valid; } unsigned short t_sys_settings::get_sip_port(bool force_active) { mtx_sys.lock(); // The configured port becomes the active port after first // usage of the port. if (!active_sip_port || force_active) { if (override_sip_port > 0) { // The port provided on the command line overrides // the configured port. active_sip_port = override_sip_port; } else { active_sip_port = config_sip_port; } } mtx_sys.unlock(); return active_sip_port; } twinkle-1.10.1/src/sys_settings.h000066400000000000000000000417231277565361200167720ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _SYS_SETTINGS_H #define _SYS_SETTINGS_H #include #include #include #include "parser/sip_message.h" #include "sockets/url.h" #include "threads/mutex.h" #include "twinkle_config.h" using namespace std; /** @name General system settings */ //@{ /** User directory, relative to the home directory ($HOME) */ #define DIR_USER ".twinkle" /** Home directory */ #define DIR_HOME (getenv("HOME")) /** Directory for storing temporary files, relative to @ref DIR_USER */ #define DIR_TMPFILE "tmp" /** Device file for DSP */ #define DEV_DSP "/dev/dsp" /** Device prefixes in settings file */ #define PFX_OSS "oss:" #define PFX_ALSA "alsa:" /** ALSA default device */ #define DEV_ALSA_DFLT "alsa:default" /** Device string for other device */ #define DEV_OTHER "other device" /** File with SIP providers for the wizard */ #define FILE_PROVIDERS "providers.csv" /** File with CLI command history */ #define FILE_CLI_HISTORY "twinkle.history" //@} /** Audio device */ class t_audio_device { public: enum t_audio_device_type { OSS, ALSA } type; string device; // eg. /dev/dsp, /dev/dsp1 for OSS or hw:0,0 for ALSA string sym_link; // real device if the device is a symbolic link string name; // name of the sound card // Get a one-line description string get_description(void) const; // Get string to be written in settings file string get_settings_value(void) const; }; /** Window geometry */ struct t_win_geometry { int x; /**< x-coordinate of top left corner */ int y; /**< y-coordinate of top left corner */ int width; /**< Window width */ int height; /**< Window height */ /** Constructor */ t_win_geometry(); /** Constructor */ t_win_geometry(int x_, int y_, int width_, int height_); /** * Construct a geometry from an encoded string. * If the string cannot be parsed, all values are set to zero. * @param value [in] Encoded string "x,y,widht,height" */ t_win_geometry(const string &value); /** * Encode geometry into a string. * @return Encoded geometry "x,y,width,height" */ string encode(void) const; }; /** System settings */ class t_sys_settings { private: // Mutex to avoid sync concurrent access mutable t_recursive_mutex mtx_sys; /** File descriptor of lock file */ int fd_lock_file; // Share directory for files applicable to all users string dir_share; // Full file name for config file string filename; /** The SIP port that is currently used */ unsigned short active_sip_port; // Sound devices t_audio_device dev_ringtone; t_audio_device dev_speaker; t_audio_device dev_mic; // Indicates if audio devices should be validated before // usage. bool validate_audio_dev; int alsa_play_period_size; int alsa_capture_period_size; int oss_fragment_size; // Log file settings unsigned short log_max_size; // in MB bool log_show_sip; bool log_show_stun; bool log_show_memory; bool log_show_debug; /** @name GUI settings */ //@{ bool gui_use_systray; bool gui_hide_on_close; /** Show main window on incoming call after a few seconds */ bool gui_auto_show_incoming; int gui_auto_show_timeout; bool gui_show_call_osd; /** Command to start an internet browser */ string gui_browser_cmd; //@} // Address book settings bool ab_show_sip_only; bool ab_lookup_name; bool ab_override_display; bool ab_lookup_photo; // Call history settings int ch_max_size; // #calls // Service settings // Call waiting allows an incoming call if one line is busy. bool call_waiting; // Indicates if both lines should be hung up when ending a // 3-way conference call. // If false, then only the active line will be hung up. bool hangup_both_3way; // Startup settings list start_user_profiles; bool start_hidden; /** The full path name of the shared mime database */ string mime_shared_database; /** @name Network settings */ //@{ /** Port for sending and receiving SIP messages. This is the value * written in the system settings file. This value can differ from * active_sip_port value if the user changed the system * settings while Twinkle is running. */ unsigned short config_sip_port; /** SIP UDP port overridden by the command options. */ unsigned short override_sip_port; /** Port for RTP. * rtp_port is the base port for RTP streams. Each phone line * uses has its own RTP port number. * line x has RTP port = rtp_port + x * 2 and * RTCP port = rtp_port + x * 2 + 1 * Where x starts at 0 * * NOTE: for call transfer scenario, line 2 (3rd line) is used * which is not a line that is visible to the user. The user * only sees 2 lines for its use. By having a dedicated port * for line 2, the RTP stream for a referred call uses another * port than the RTP stream for an original call, preventing * the RTP streams for these calls to become mixed. * * NOTE: during a call transfer, line 2 will be swapped with another * line, so the ports swap accordingly. */ unsigned short rtp_port; /** RTP port overridden by the command options. */ unsigned short override_rtp_port; /** Maximum size of a SIP message received over UDP. */ unsigned short sip_max_udp_size; /** Maximum size of a SIP message received over TCP. */ unsigned long sip_max_tcp_size; //@} // Ring tone settings bool play_ringtone; string ringtone_file; bool play_ringback; string ringback_file; // Persistent storage for user interface state // The profile that was last used before Twinkle was terminated. string last_used_profile; // Call information for redial last call function t_url redial_url; string redial_display; string redial_subject; string redial_profile; // profile used to make the call bool redial_hide_user; // Did the user request hiding? // History of latest dialed addresses list dial_history; /** @name GUI view settings */ //@{ bool show_display; bool compact_line_status; bool show_buddy_list; //@} /** @name Settings to restore a previous user interface session after system shutdown */ //@{ /** ID of previous session */ string ui_session_id; /** Active user profiles */ list ui_session_active_profiles; /** Geometry of main window */ t_win_geometry ui_session_main_geometry; /** Flag to indicate if the main window is hidden. */ bool ui_session_main_hidden; /** Window state of main window. */ unsigned int ui_session_main_state; //@} // One time warnings bool warn_hide_user; // Warn use that provider may not support hiding. public: /** Constructor */ t_sys_settings(); /** @name Getters */ //@{ t_audio_device get_dev_ringtone(void) const; t_audio_device get_dev_speaker(void) const; t_audio_device get_dev_mic(void) const; bool get_validate_audio_dev(void) const; int get_alsa_play_period_size(void) const; int get_alsa_capture_period_size(void) const; int get_oss_fragment_size(void) const; unsigned short get_log_max_size(void) const; bool get_log_show_sip(void) const; bool get_log_show_stun(void) const; bool get_log_show_memory(void) const; bool get_log_show_debug(void) const; bool get_gui_use_systray(void) const; bool get_gui_hide_on_close(void) const; bool get_gui_auto_show_incoming(void) const; int get_gui_auto_show_timeout(void) const; string get_gui_browser_cmd(void) const; bool get_gui_show_call_osd() const; bool get_ab_show_sip_only(void) const; bool get_ab_lookup_name(void) const; bool get_ab_override_display(void) const; bool get_ab_lookup_photo(void) const; int get_ch_max_size(void) const; bool get_call_waiting(void) const; bool get_hangup_both_3way(void) const; list get_start_user_profiles(void) const; bool get_start_hidden(void) const; unsigned short get_config_sip_port(void) const; unsigned short get_rtp_port(void) const; unsigned short get_sip_max_udp_size(void) const; unsigned long get_sip_max_tcp_size(void) const; bool get_play_ringtone(void) const; string get_ringtone_file(void) const; bool get_play_ringback(void) const; string get_ringback_file(void) const; string get_last_used_profile(void) const; t_url get_redial_url(void) const; string get_redial_display(void) const; string get_redial_subject(void) const; string get_redial_profile(void) const; bool get_redial_hide_user(void) const; list get_dial_history(void) const; bool get_show_display(void) const; bool get_compact_line_status(void) const; bool get_show_buddy_list(void) const; string get_ui_session_id(void) const; list get_ui_session_active_profiles(void) const; t_win_geometry get_ui_session_main_geometry(void) const; bool get_ui_session_main_hidden(void) const; unsigned int get_ui_session_main_state(void) const; bool get_warn_hide_user(void) const; string get_mime_shared_database(void) const; //@} /** @name Setters */ //@{ void set_dev_ringtone(const t_audio_device &dev); void set_dev_speaker(const t_audio_device &dev); void set_dev_mic(const t_audio_device &dev); void set_validate_audio_dev(bool b); void set_alsa_play_period_size(int size); void set_alsa_capture_period_size(int size); void set_oss_fragment_size(int size); void set_log_max_size(unsigned short size); void set_log_show_sip(bool b); void set_log_show_stun(bool b); void set_log_show_memory(bool b); void set_log_show_debug(bool b); void set_gui_use_systray(bool b); void set_gui_hide_on_close(bool b); void set_gui_auto_show_incoming(bool b); void set_gui_auto_show_timeout(int timeout); void set_gui_browser_cmd(const string &s); void set_gui_show_call_osd(bool b); void set_ab_show_sip_only(bool b); void set_ab_lookup_name(bool b); void set_ab_override_display(bool b); void set_ab_lookup_photo(bool b); void set_ch_max_size(int size); void set_call_waiting(bool b); void set_hangup_both_3way(bool b); void set_start_user_profiles(const list &profiles); void set_start_hidden(bool b); void set_config_sip_port(unsigned short port); void set_override_sip_port(unsigned short port); void set_rtp_port(unsigned short port); void set_override_rtp_port(unsigned short port); void set_sip_max_udp_size(unsigned short size); void set_sip_max_tcp_size(unsigned long size); void set_play_ringtone(bool b); void set_ringtone_file(const string &file); void set_play_ringback(bool b); void set_ringback_file(const string &file); void set_last_used_profile(const string &profile); void set_redial_url(const t_url &url); void set_redial_display(const string &display); void set_redial_subject(const string &subject); void set_redial_profile(const string &profile); void set_redial_hide_user(bool b); void set_dial_history(const list &history); void set_show_display(bool b); void set_compact_line_status(bool b); void set_show_buddy_list(bool b); void set_ui_session_id(const string &id); void set_ui_session_active_profiles(const list &profiles); void set_ui_session_main_geometry(const t_win_geometry &geometry); void set_ui_session_main_hidden(bool hidden); void set_ui_session_main_state(unsigned int state); void set_warn_hide_user(bool b); void set_mime_shared_database(const string &filename); //@} /** * Get "about" text. * @param html [in] Indicates if "about" text must be in HTML format. * @return The "about" text" */ string about(bool html) const; /** * Get produce release date. * @return product release date in locale format */ string get_product_date(void) const; /** * Get a string of options that are built, e.g. ALSA, KDE * @return The string of options. */ string get_options_built(void) const; /** * Check if the environment of the machine satisfies all requirements. * @param error_msg [out] User readable error message when false is returned. * @return true if all requirements are met. * @return false, otherwise and error_msg contains an appropriate * error message to show the user. */ bool check_environment(string &error_msg) const; /** * Set the share directory * @param dir [in] Absolute path of the share directory. */ void set_dir_share(const string &dir); /** * Get the share directory. * @return Absolute path of the directory with shared files. */ string get_dir_share(void) const; /** * Get the directory containing language translation files. * @return Absolute path of the language directory. */ string get_dir_lang(void) const; /** * Get the user directory. * @return Absolute path of the user directory. */ string get_dir_user(void) const; /** * Get the CLI command history file. * @return Full pathname of the history file. */ string get_history_file(void) const; /** * Get the temporary file directory. * @return The full pathname of the temporary file directory. */ string get_dir_tmpfile(void) const; /** * Check if a file is located in the temporary file directory. * @return true if the file is in the temporary file directory, false otherwise. */ bool is_tmpfile(const string &filename) const; /** * Save data to a temporary file. * @param data [in] Data to save. * @param file_extension [in] Extension (glob) for file name. * @param filename [out] File name of save file, relative to the tmp directory. * @param error_msg [out] If saving failed, then this parameter contains an * error message. * @return true if saving succeeded, false otherwise. */ bool save_tmp_file(const string &data, const string &file_extension, string &filename, string &error_msg); /** * Save the body of a SIP message to a temporary file. * @param sip_msg [in] The SIP message from which the body must be saved. * @param suggested_file_extension [in] File extension (glob) for file name to save * if an extension cannot be determined from a filename supplied as * in the Content-Disposition header. * @param tmpname [out] The name of the saved file. * @param save_as_name [out] Suggested file name for user for saving. * @param error_msg [out] Error message when saving failed. * @return true if saving succeeded, false otherwise. */ bool save_sip_body(const t_sip_message &sip_msg, const string &suggested_file_extension, string &tmpname, string &save_as_name, string &error_msg); /** Remove all files from the temporary file directory */ void remove_all_tmp_files(void) const; /** @name Lock file operations */ /** * Create a lock file if it does not exist yet and take a file lock on it. * @param shared_lock [in] Indicates if the file lock must be shared or exclusive. * A shared lock is needed when the users forces multiple Twinkle processes * to run. * @param error_msg [out] Error message if the operation fails. * @param already_running [out] If the operation fails, this flag indicates Twinkle * is already running. * @return True if the file is locked successfully. * @return False if the file could not be locked. */ bool create_lock_file(bool shared_lock, string &error_msg, bool &already_running); /** Unlock the lock file. */ void delete_lock_file(void); // Read and parse a config file into the t_sys_settings object. // Returns false if it fails. error_msg is an error message that can // be give to the user. bool read_config(string &error_msg); // Write the settings into a config file bool write_config(string &error_msg); // Get all OSS devices list get_oss_devices(bool playback) const; #ifdef HAVE_LIBASOUND // Get all ALSA devices list get_alsa_devices(bool playback) const; #endif // Get all audio devices list get_audio_devices(bool playback) const; // Check if two OSS devices are equal bool equal_audio_dev(const t_audio_device &dev1, const t_audio_device &dev2) const; static t_audio_device audio_device(string device = ""); // Check validate the audio devices flagged as true. // If audio validation is turned off then always true is returned. bool exec_audio_validation(bool ringtone, bool speaker, bool mic, string &error_msg) const; // Get the active value of the SIP UDP port // Once the SIP UDP port is retrieved from the system settings, it // is stored as the active port. A next call to get_sip_port // returns the active port, even when the SIP UDP port in the settings // has changed. // If force_active == true, then always the SIP UDP port is returned // and made active unsigned short get_sip_port(bool force_active = false); }; extern t_sys_settings *sys_config; #endif twinkle-1.10.1/src/threads/000077500000000000000000000000001277565361200155065ustar00rootroot00000000000000twinkle-1.10.1/src/threads/CMakeLists.txt000066400000000000000000000002371277565361200202500ustar00rootroot00000000000000project(libtwinkle-threads) set(LIBTWINKLE_THREADS-SRCS thread.cpp mutex.cpp sema.cpp ) add_library(libtwinkle-threads OBJECT ${LIBTWINKLE_THREADS-SRCS}) twinkle-1.10.1/src/threads/mutex.cpp000066400000000000000000000054031277565361200173560ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include "mutex.h" #include "thread.h" #include using namespace std; /////////////////////////// // t_mutex /////////////////////////// t_mutex::t_mutex() { pthread_mutex_init(&mutex, NULL); } t_mutex::t_mutex(bool recursive) { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); int ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP); if (ret != 0) throw string( "t_mutex::t_mutex failed to create a recursive mutex."); pthread_mutex_init(&mutex, &attr); pthread_mutexattr_destroy(&attr); } t_mutex::~t_mutex() { pthread_mutex_destroy(&mutex); } void t_mutex::lock(void) { int ret = pthread_mutex_lock(&mutex); if (ret != 0) throw string("t_mutex::lock failed."); } int t_mutex::trylock(void) { int ret = pthread_mutex_trylock(&mutex); switch (ret) { case 0: case EBUSY: return ret; default: throw string("t_mutex::trylock failed."); } } void t_mutex::unlock(void) { int ret = pthread_mutex_unlock(&mutex); if (ret != 0) throw ("t_mutex::unlock failed."); } /////////////////////////// // t_recursive_mutex /////////////////////////// t_recursive_mutex::t_recursive_mutex() : t_mutex(true) {} t_recursive_mutex::~t_recursive_mutex() {} /////////////////////////// // t_guard_mutex /////////////////////////// t_mutex_guard::t_mutex_guard(t_mutex &mutex) : mutex_(mutex) { mutex_.lock(); } t_mutex_guard::~t_mutex_guard() { mutex_.unlock(); } /////////////////////////// // t_rwmutex /////////////////////////// t_rwmutex::t_rwmutex() { int ret = pthread_rwlock_init(&_lock, nullptr); if (ret != 0) throw string( "t_rwmutex::t_rwmutex failed to create a r/w mutex."); } t_rwmutex::~t_rwmutex() { pthread_rwlock_destroy(&_lock); } void t_rwmutex::lockRead() { int err = pthread_rwlock_rdlock(&_lock); if (err != 0) throw std::logic_error("Mutex lock failed"); } void t_rwmutex::lockWrite() { int err = pthread_rwlock_wrlock(&_lock); if (err != 0) throw std::logic_error("Mutex lock failed"); } void t_rwmutex::unlock() { pthread_rwlock_unlock(&_lock); } twinkle-1.10.1/src/threads/mutex.h000066400000000000000000000053341277565361200170260ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _H_MUTEX #define _H_MUTEX #include #include // #include /** * @file * Mutex operations */ class t_mutex { protected: pthread_mutex_t mutex; public: t_mutex(); // Throws a string exception (error message) when failing. t_mutex(bool recursive); virtual ~t_mutex(); // These methods throw a string exception when the operation // fails. virtual void lock(void); // Returns: // 0 - success // EBUSY - already locked // For other errors a string exception is thrown virtual int trylock(void); virtual void unlock(void); }; class t_recursive_mutex : public t_mutex { public: t_recursive_mutex(); ~t_recursive_mutex(); }; /** * Guard pattern for a mutex . * The constructor of a guard locks a mutex and the destructor * unlocks it. This way a guard object can be created at entrance * of a function. Then at exit, the mutex is automically unlocked * as the guard object goes out of scope. */ class t_mutex_guard { private: /** The guarding mutex. */ t_mutex &mutex_; public: /** * The constructor will lock the mutex. * @param mutex [in] Mutex to lock. */ t_mutex_guard(t_mutex &mutex); /** * The destructor will unlock the mutex. */ ~t_mutex_guard(); }; class t_rwmutex { protected: pthread_rwlock_t _lock; public: t_rwmutex(); ~t_rwmutex(); void lockRead(); void lockWrite(); void unlock(); }; class t_rwmutex_reader { private: t_rwmutex& _mutex; public: t_rwmutex_reader(t_rwmutex& mutex) : _mutex(mutex) { // std::cout << "mtx rd lock " << (void*)&_mutex << std::endl; _mutex.lockRead(); } ~t_rwmutex_reader() { // std::cout << "mtx rd unlock " << (void*)&_mutex << std::endl; _mutex.unlock(); } }; class t_rwmutex_writer { private: t_rwmutex& _mutex; public: t_rwmutex_writer(t_rwmutex& mutex) : _mutex(mutex) { // std::cout << "mtx wr lock " << (void*)&_mutex << std::endl; _mutex.lockWrite(); } ~t_rwmutex_writer() { // std::cout << "mtx wr unlock " << (void*)&_mutex << std::endl; _mutex.unlock(); } }; #endif twinkle-1.10.1/src/threads/sema.cpp000066400000000000000000000034031277565361200171370ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include "sema.h" #include "util.h" using namespace std; t_semaphore::t_semaphore(unsigned int value) { int ret; ret = sem_init(&sem, 0, value); if (ret != 0) { string err = get_error_str(errno); string exception = "t_semaphore::t_semaphore failed to create a semaphore.\n"; exception += err; throw exception; } } t_semaphore::~t_semaphore() { sem_destroy(&sem); } void t_semaphore::up(void) { int ret; ret = sem_post(&sem); if (ret != 0) { string err = get_error_str(errno); string exception = "t_semaphore::up failed.\n"; exception += err; throw exception; } } void t_semaphore::down(void) { while (true) { int ret = sem_wait(&sem); if (ret != 0 && errno == EINTR) { // In NPTL threading sem_wait can be interrupted. // In LinuxThreads threading sem_wait is non-interruptable. // Continue with sem_wait if an interrupt is caught. continue; } break; } } bool t_semaphore::try_down(void) { int ret; ret = sem_trywait(&sem); return (ret == 0); } twinkle-1.10.1/src/threads/sema.h000066400000000000000000000022541277565361200166070ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _H_SEMAPHORE #define _H_SEMAPHORE #include class t_semaphore { private: sem_t sem; public: // Throws a string exception (error message) when failing. t_semaphore(unsigned int value); ~t_semaphore(); // Throws a string exception (error message) when failing. void up(void); void down(void); // Returns true if downing the semaphore succeeded. // Returns false if the semaphore was zero already. bool try_down(void); }; #endif twinkle-1.10.1/src/threads/thread.cpp000066400000000000000000000047671277565361200174770ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include #include "thread.h" // Scratch variables for checking LinuxThreads vs NPTL static pid_t pid_thread; t_thread::t_thread(void *(*start_routine)(void *), void *arg) { int ret; ret = pthread_create(&tid, NULL, start_routine, arg); if (ret != 0) throw ret; } void t_thread::join(void) { int ret = pthread_join(tid, NULL); if (ret != 0) throw ret; } void t_thread::detach(void) { int ret = pthread_detach(tid); if (ret != 0) throw ret; } void t_thread::kill(void) { int ret = pthread_kill(tid, SIGKILL); if (ret != 0) throw ret; } void t_thread::cancel(void) { int ret = pthread_cancel(tid); if (ret != 0) throw ret; } void t_thread::set_sched_fifo(int priority) { struct sched_param sp; sp.sched_priority = priority; int ret = pthread_setschedparam(tid, SCHED_FIFO, &sp); if (ret != 0) throw ret; } pthread_t t_thread::get_tid(void) const { return tid; } pthread_t t_thread::self(void) { return pthread_self(); } bool t_thread::is_equal(const t_thread &thr) const { return pthread_equal(tid, thr.get_tid()); } bool t_thread::is_equal(const pthread_t &_tid) const { return pthread_equal(tid, _tid); } bool t_thread::is_self(void) const { return pthread_equal(tid, pthread_self()); } bool t_thread::is_self(const pthread_t &_tid) { return pthread_equal(_tid, pthread_self()); } void *check_threading_impl(void *arg) { pid_thread = getpid(); pthread_exit(NULL); } bool t_thread::is_LinuxThreads(void) { t_thread *thr = new t_thread(check_threading_impl, NULL); try { thr->join(); } catch (int) { // Thread is already terminated. } delete thr; // In LinuxThreads each thread has a different pid. // In NPTL all threads have the same pid. return (getpid() != pid_thread); } twinkle-1.10.1/src/threads/thread.h000066400000000000000000000033741277565361200171350ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _H_THREAD #define _H_THREAD #include class t_thread { private: pthread_t tid; // Thread id public: t_thread(void *(*start_routine)(void *), void *arg); // These methods throw an int (return value of libpthread function) // when they fail void join(void); void detach(void); void kill(void); void cancel(void); void set_sched_fifo(int priority); // Get thread id pthread_t get_tid(void) const; // Get thread id of current thread static pthread_t self(void); // Check if 2 threads are equal bool is_equal(const t_thread &thr) const; bool is_equal(const pthread_t &_tid) const; bool is_self(void) const; static bool is_self(const pthread_t &_tid); // Check if LinuxThreads or NPTL is active. This check is needed // for correctly handling signals. Signal handling in LinuxThreads // is quite different from signal handling in NPTL. // This checks creates a new thread and waits on its termination, // so you better cache its result for efficient future checks. static bool is_LinuxThreads(void); }; #endif twinkle-1.10.1/src/timekeeper.cpp000066400000000000000000000444301277565361200167170ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include "events.h" #include "line.h" #include "log.h" #include "phone.h" #include "subscription.h" #include "timekeeper.h" #include "transaction_mgr.h" #include "threads/thread.h" #include "audits/memman.h" extern t_phone *phone; extern t_event_queue *evq_trans_layer; extern t_event_queue *evq_trans_mgr; extern t_event_queue *evq_timekeeper; extern t_timekeeper *timekeeper; extern bool threading_is_LinuxThreads; string timer_type2str(t_timer_type t) { switch(t) { case TMR_TRANSACTION: return "TMR_TRANSACTION"; case TMR_PHONE: return "TMR_PHONE"; case TMR_LINE: return "TMR_LINE"; case TMR_SUBSCRIBE: return "TMR_SUBSCRIBE"; case TMR_PUBLISH: return "TMR_PUBLISH"; case TMR_STUN_TRANSACTION: return "TMR_STUN_TRANSACTION"; } return "UNKNOWN"; } /////////////////////////////////////////////////////////// // class t_timer /////////////////////////////////////////////////////////// t_timer::t_timer(long dur) : t_id_object() { long d = dur; // HACK: if a timer is set to zero seconds, set it to 1 ms, otherwise // the timer will not expire. if (dur == 0) d++; duration = d; relative_duration = d; } long t_timer::get_duration(void) const { return duration; } long t_timer::get_relative_duration(void) const { return relative_duration; } void t_timer::set_relative_duration(long d) { relative_duration = d; } /////////////////////////////////////////////////////////// // class t_tmr_transaction /////////////////////////////////////////////////////////// t_tmr_transaction::t_tmr_transaction(long dur, t_sip_timer tmr, unsigned short tid) : t_timer(dur) { sip_timer = tmr; transaction_id = tid; } void t_tmr_transaction::expired(void) { // Create a timeout event for the transaction manager evq_trans_mgr->push_timeout(this); } t_timer *t_tmr_transaction::copy(void) const { t_tmr_transaction *t = new t_tmr_transaction(*this); MEMMAN_NEW(t); return t; } t_timer_type t_tmr_transaction::get_type(void) const { return TMR_TRANSACTION; } unsigned short t_tmr_transaction::get_tid(void) const { return transaction_id; } t_sip_timer t_tmr_transaction::get_sip_timer(void) const { return sip_timer; } string t_tmr_transaction::get_name(void) const { switch(sip_timer) { case TIMER_T1: return "TIMER_T1"; case TIMER_T2: return "TIMER_T2"; case TIMER_T4: return "TIMER_T4"; case TIMER_A: return "TIMER_A"; case TIMER_B: return "TIMER_B"; case TIMER_C: return "TIMER_C"; case TIMER_D: return "TIMER_D"; case TIMER_E: return "TIMER_E"; case TIMER_F: return "TIMER_F"; case TIMER_G: return "TIMER_G"; case TIMER_H: return "TIMER_H"; case TIMER_I: return "TIMER_I"; case TIMER_J: return "TIMER_J"; case TIMER_K: return "TIMER_K"; } return "UNKNOWN"; } /////////////////////////////////////////////////////////// // class t_tmr_phone /////////////////////////////////////////////////////////// t_tmr_phone::t_tmr_phone(long dur, t_phone_timer ptmr, t_phone *p) : t_timer(dur) { phone_timer = ptmr; the_phone = p; } void t_tmr_phone::expired(void) { evq_trans_layer->push_timeout(this); } t_timer *t_tmr_phone::copy(void) const { t_tmr_phone *t = new t_tmr_phone(*this); MEMMAN_NEW(t); return t; } t_timer_type t_tmr_phone::get_type(void) const { return TMR_PHONE; } t_phone_timer t_tmr_phone::get_phone_timer(void) const { return phone_timer; } t_phone *t_tmr_phone::get_phone(void) const { return the_phone; } string t_tmr_phone::get_name(void) const { switch(phone_timer) { case PTMR_REGISTRATION: return "PTMR_REGISTRATION"; case PTMR_NAT_KEEPALIVE: return "PTMR_NAT_KEEPALIVE"; case PTMR_TCP_PING: return "PTMR_TCP_PING"; } return "UNKNOWN"; } /////////////////////////////////////////////////////////// // class t_tmr_line /////////////////////////////////////////////////////////// t_tmr_line::t_tmr_line(long dur, t_line_timer ltmr, t_object_id lid, t_object_id d) : t_timer(dur) { line_timer = ltmr; line_id = lid; dialog_id = d; } void t_tmr_line::expired(void) { evq_trans_layer->push_timeout(this); } t_timer *t_tmr_line::copy(void) const { t_tmr_line *t = new t_tmr_line(*this); MEMMAN_NEW(t); return t; } t_timer_type t_tmr_line::get_type(void) const { return TMR_LINE; } t_line_timer t_tmr_line::get_line_timer(void) const { return line_timer; } t_object_id t_tmr_line::get_line_id(void) const { return line_id; } t_object_id t_tmr_line::get_dialog_id(void) const { return dialog_id; } string t_tmr_line::get_name(void) const { switch(line_timer) { case LTMR_ACK_TIMEOUT: return "LTMR_ACK_TIMEOUT"; case LTMR_ACK_GUARD: return "LTMR_ACK_GUARD"; case LTMR_INVITE_COMP: return "LTMR_INVITE_COMP"; case LTMR_NO_ANSWER: return "LTMR_NO_ANSWER"; case LTMR_RE_INVITE_GUARD: return "LTMR_RE_INVITE_GUARD"; case LTMR_100REL_TIMEOUT: return "LTMR_100REL_TIMEOUT"; case LTMR_100REL_GUARD: return "LTMR_100REL_GUARD"; case LTMR_CANCEL_GUARD: return "LTMR_CANCEL_GUARD"; case LTMR_GLARE_RETRY: return "LTMR_GLARE_RETRY"; } return "UNKNOWN"; } /////////////////////////////////////////////////////////// // class t_tmr_subscribe /////////////////////////////////////////////////////////// t_tmr_subscribe::t_tmr_subscribe(long dur, t_subscribe_timer stmr, t_object_id lid, t_object_id d, const string &event_type, const string &event_id) : t_timer(dur) { subscribe_timer = stmr; line_id = lid; dialog_id = d; sub_event_type = event_type; sub_event_id = event_id; } void t_tmr_subscribe::expired(void) { evq_trans_layer->push_timeout(this); } t_timer *t_tmr_subscribe::copy(void) const { t_tmr_subscribe *t = new t_tmr_subscribe(*this); MEMMAN_NEW(t); return t; } t_timer_type t_tmr_subscribe::get_type(void) const { return TMR_SUBSCRIBE; } t_subscribe_timer t_tmr_subscribe::get_subscribe_timer(void) const { return subscribe_timer; } t_object_id t_tmr_subscribe::get_line_id(void) const { return line_id; } t_object_id t_tmr_subscribe::get_dialog_id(void) const { return dialog_id; } string t_tmr_subscribe::get_sub_event_type(void) const { return sub_event_type; } string t_tmr_subscribe::get_sub_event_id(void) const { return sub_event_id; } string t_tmr_subscribe::get_name(void) const { switch(subscribe_timer) { case STMR_SUBSCRIPTION: return "STMR_SUBSCRIPTION"; } return "UNKNOWN"; } /////////////////////////////////////////////////////////// // class t_tmr_publish /////////////////////////////////////////////////////////// t_tmr_publish::t_tmr_publish(long dur, t_publish_timer ptmr, const string &_event_type) : t_timer(dur), publish_timer(ptmr), event_type(_event_type) {} void t_tmr_publish::expired(void) { evq_trans_layer->push_timeout(this); } t_timer *t_tmr_publish::copy(void) const { t_tmr_publish *t = new t_tmr_publish(*this); MEMMAN_NEW(t); return t; } t_timer_type t_tmr_publish::get_type(void) const { return TMR_PUBLISH; } t_publish_timer t_tmr_publish::get_publish_timer(void) const { return publish_timer; } string t_tmr_publish::get_name(void) const { switch (publish_timer) { case PUBLISH_TMR_PUBLICATION: return "PUBLISH_TMR_PUBLICATION"; } return "UNKNOWN"; } /////////////////////////////////////////////////////////// // class t_tmr_stun_trans /////////////////////////////////////////////////////////// t_tmr_stun_trans::t_tmr_stun_trans(long dur, t_stun_timer tmr, unsigned short tid) : t_timer(dur) { stun_timer = tmr; transaction_id = tid; } void t_tmr_stun_trans::expired(void) { // Create a timeout event for the transaction manager evq_trans_mgr->push_timeout(this); } t_timer *t_tmr_stun_trans::copy(void) const { t_tmr_stun_trans *t = new t_tmr_stun_trans(*this); MEMMAN_NEW(t); return t; } t_timer_type t_tmr_stun_trans::get_type(void) const { return TMR_STUN_TRANSACTION; } unsigned short t_tmr_stun_trans::get_tid(void) const { return transaction_id; } t_stun_timer t_tmr_stun_trans::get_stun_timer(void) const { return stun_timer; } string t_tmr_stun_trans::get_name(void) const { switch(stun_timer) { case STUN_TMR_REQ_TIMEOUT: return "STUN_TMR_REQ_TIMEOUT"; } return "UNKNOWN"; } /////////////////////////////////////////////////////////// // class t_timekeeper /////////////////////////////////////////////////////////// t_timekeeper::t_timekeeper() : mutex() { stopped = false; timer_expired = false; } void t_timekeeper::start(void (*timeout_handler)(int)) { signal(SIGALRM, timeout_handler); } t_timekeeper::~t_timekeeper() { struct itimerval itimer; mutex.lock(); log_file->write_header("t_timekeeper::~t_timekeeper", LOG_NORMAL, LOG_INFO); log_file->write_raw("Clean up timekeeper.\n"); // Stop timers itimer.it_interval.tv_sec = 0; itimer.it_interval.tv_usec = 0; itimer.it_value.tv_sec = 0; itimer.it_value.tv_usec = 0; setitimer(ITIMER_REAL, &itimer, NULL); for (list::iterator i = timer_list.begin(); i != timer_list.end(); i++) { log_file->write_raw("\nDeleting timer:\n"); log_file->write_raw("Id: "); log_file->write_raw((*i)->get_object_id()); log_file->write_raw(", Type: "); log_file->write_raw(timer_type2str((*i)->get_type())); log_file->write_raw(", Timer: "); log_file->write_raw((*i)->get_name()); log_file->write_raw("\nDuration: "); log_file->write_raw((*i)->get_duration()); log_file->write_raw(", Relative duration: "); log_file->write_raw((*i)->get_relative_duration()); log_file->write_endl(); if ((*i)->get_type() == TMR_TRANSACTION) { log_file->write_raw("Transaction id: "); log_file->write_raw( ((t_tmr_transaction *)(*i))->get_tid()); log_file->write_endl(); } MEMMAN_DELETE(*i); delete *i; } if (threading_is_LinuxThreads) { signal(SIGALRM, SIG_DFL); } log_file->write_footer(); mutex.unlock(); } void t_timekeeper::lock(void) { mutex.lock(); } void t_timekeeper::unlock(void) { mutex.unlock(); if (timer_expired) { timer_expired = false; report_expiry(); } } void t_timekeeper::start_timer(t_timer *t) { struct itimerval itimer; long remain_msec; lock(); // The next interval option is not used itimer.it_interval.tv_sec = 0; itimer.it_interval.tv_usec = 0; // Get duration of the timer to start long d = t->get_relative_duration(); // If no timer is currently running then simply start the timer if (timer_list.empty()) { timer_list.push_back(t); itimer.it_value.tv_sec = d / 1000; itimer.it_value.tv_usec = (d % 1000) * 1000; setitimer(ITIMER_REAL, &itimer, NULL); unlock(); return; } // Get remaining duration of current running timer getitimer(ITIMER_REAL, &itimer); remain_msec = itimer.it_value.tv_sec * 1000 + itimer.it_value.tv_usec / 1000; // If the new timer is shorter than the current timer. // then the new timer should be run first. if (d < remain_msec) { // Change running timer to new timer itimer.it_value.tv_sec = d / 1000; itimer.it_value.tv_usec = (d % 1000) * 1000; setitimer(ITIMER_REAL, &itimer, NULL); // Calculate the relative duration the timer // that was running. t_timer *old_timer = timer_list.front(); old_timer->set_relative_duration(remain_msec - d); // Add new timer at the front of the list timer_list.push_front(t); unlock(); return; } // Calculate the relative duration for the new timer long new_duration = d - remain_msec; // Insert the new timer at the right position in the list. list::iterator i; for (i = timer_list.begin(); i != timer_list.end(); i++) { // skip the first timer if (i == timer_list.begin()) continue; long dur = (*i)->get_relative_duration(); if (new_duration < dur) { // Adjust relative duration existing timer (*i)->set_relative_duration(dur - new_duration); // Insert new timer before existing timer t->set_relative_duration(new_duration); timer_list.insert(i, t); unlock(); return; } new_duration -= dur; } // Add the new timer to the end of the list t->set_relative_duration(new_duration); timer_list.push_back(t); unlock(); } void t_timekeeper::stop_timer(t_object_id id) { struct itimerval itimer; long remain_msec; lock(); // The next interval option is not used itimer.it_interval.tv_sec = 0; itimer.it_interval.tv_usec = 0; if (timer_list.empty()) { // Timer already expired or stopped unlock(); return; } // Find timer list::iterator i = timer_list.begin(); while (i != timer_list.end()) { if ((*i)->get_object_id() == id) break; i++; } if (i == timer_list.end()) { // Timer already expired or stopped. unlock(); return; } // If it is the current running timer, then it must be stopped if (i == timer_list.begin()) { getitimer(ITIMER_REAL, &itimer); // If remaining time is less then 100 msec then let it // expire to prevent race condition when timer expires // while stopping it now. remain_msec = itimer.it_value.tv_sec * 1000 + itimer.it_value.tv_usec / 1000; if (remain_msec < 100) { stopped = true; unlock(); return; } // Stop timer itimer.it_value.tv_sec = 0; itimer.it_value.tv_usec = 0; setitimer(ITIMER_REAL, &itimer, NULL); // Remove the timer MEMMAN_DELETE(timer_list.front()); delete timer_list.front(); timer_list.pop_front(); // If a next timer exists then adjust its relative // duration and start it. if (!timer_list.empty()) { t_timer *next_timer = timer_list.front(); long dur = next_timer->get_relative_duration(); dur += remain_msec; next_timer->set_relative_duration(dur); itimer.it_value.tv_sec = dur / 1000; itimer.it_value.tv_usec = (dur % 1000) * 1000; setitimer(ITIMER_REAL, &itimer, NULL); } unlock(); return; } // Timer is not the current running timer, so delete it // and adjust relative duration of the next timer. list::iterator next = i; next++; if (next == timer_list.end()) { // There is no next timer MEMMAN_DELETE(timer_list.back()); delete timer_list.back(); timer_list.pop_back(); unlock(); return; } long dur = (*i)->get_relative_duration(); long dur_next = (*next)->get_relative_duration(); (*next)->set_relative_duration(dur + dur_next); MEMMAN_DELETE(*i); delete *i; timer_list.erase(i); unlock(); } void t_timekeeper::report_expiry(void) { lock(); if (timer_list.empty()) { unlock(); return; } t_timer *t = timer_list.front(); // Trigger action if timer was not stopped if (!stopped) { t->expired(); } stopped = false; // Remove the timer MEMMAN_DELETE(timer_list.front()); delete timer_list.front(); timer_list.pop_front(); if (timer_list.empty()) { unlock(); return; } // If the relative duration of the next timer is 0, then // it also expired. Action should be triggerd. If not, then // it should be started. t_timer *next = timer_list.front(); long dur = next->get_relative_duration(); while (dur == 0) { next->expired(); MEMMAN_DELETE(next); delete next; timer_list.pop_front(); if (timer_list.empty()) break; next = timer_list.front(); dur = next->get_relative_duration(); } if (!timer_list.empty()) { struct itimerval itimer; itimer.it_interval.tv_sec = 0; itimer.it_interval.tv_usec = 0; itimer.it_value.tv_sec = dur / 1000; itimer.it_value.tv_usec = (dur % 1000) * 1000; setitimer(ITIMER_REAL, &itimer, NULL); } unlock(); } unsigned long t_timekeeper::get_remaining_time(t_object_id timer_id) { struct itimerval itimer; unsigned long remain_msec = 0; unsigned long duration = 0; lock(); // The next interval option is not used itimer.it_interval.tv_sec = 0; itimer.it_interval.tv_usec = 0; // Get remaining duration of current running timer getitimer(ITIMER_REAL, &itimer); remain_msec = itimer.it_value.tv_sec * 1000 + itimer.it_value.tv_usec / 1000; // Find the timer list::iterator i = timer_list.begin(); while (i != timer_list.end()) { if (i != timer_list.begin()) { remain_msec += (*i)->get_relative_duration(); } if ((*i)->get_object_id() == timer_id) break; i++; } // Return duration to originator of get event if (i == timer_list.end()) { duration = 0; } else { duration = remain_msec; } unlock(); return duration; } // SIGALRM handler void timeout_handler(int signum) { signal(SIGALRM, timeout_handler); // timekeeper.report_expiry(); // This will signal an interrupt to the call to pop in the // main look t_timekeeper::run evq_timekeeper->interrupt(); } void t_timekeeper::run(void) { t_event *event; t_event_start_timer *ev_start; t_event_stop_timer *ev_stop; bool timeout; // The timekeeper should not try to take the phone lock as // it may lead to a deadlock. Make sure an assert is raised // if this situation ever happens. phone->add_prohibited_thread(); if (threading_is_LinuxThreads) { // In LinuxThreads SIGALRM caused by the expiration of a timer // started with setitimer is always delivered to the thread calling // setitimer. So the sigwait() call from another thread does not // work. Use a signal handler instead. start(timeout_handler); } bool quit = false; while (!quit) { event = evq_timekeeper->pop(timeout); if (timeout) { report_expiry(); continue; } switch(event->get_type()) { case EV_START_TIMER: ev_start = (t_event_start_timer *)event; start_timer(ev_start->get_timer()); break; case EV_STOP_TIMER: ev_stop = (t_event_stop_timer *)event; stop_timer(ev_stop->get_timer_id()); break; case EV_QUIT: quit = true; break; default: assert(false); } MEMMAN_DELETE(event); delete event; } } void *timekeeper_main(void *arg) { timekeeper->run(); return NULL; } void *timekeeper_sigwait(void *arg) { sigset_t sigset; int sig; sigemptyset(&sigset); sigaddset(&sigset, SIGALRM); while (true) { // When SIGCONT is received after SIGSTOP, sigwait returns // with EINTR ?? if (sigwait(&sigset, &sig) == EINTR) continue; evq_timekeeper->interrupt(); } return NULL; } twinkle-1.10.1/src/timekeeper.h000066400000000000000000000172641277565361200163710ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _TIMEKEEPER_H #define _TIMEKEEPER_H #include #include "id_object.h" #include "protocol.h" #include "transaction.h" #include "threads/mutex.h" #include "threads/sema.h" using namespace std; // Forward declarations class t_phone; class t_line; class t_subscription; /** Timer type */ enum t_timer_type { TMR_TRANSACTION, /**< Transaction timer */ TMR_PHONE, /**< Timer associated with the phone */ TMR_LINE, /**< Timer associated with a line */ TMR_SUBSCRIBE, /**< Subscription timer */ TMR_PUBLISH, /**< Publication timer */ TMR_STUN_TRANSACTION /**< STUN timer */ }; //////////////////////////////////////////////////////////////// // General timer. //////////////////////////////////////////////////////////////// // Instances should be created from subclasses. class t_timer : public t_id_object { private: long duration; // milliseconds long relative_duration; // milliseconds public: t_timer(long dur); virtual ~t_timer() {} // This method is invoked on expiry // Subclasses should implent the action to be taken. virtual void expired(void) = 0; long get_duration(void) const; long get_relative_duration(void) const; void set_relative_duration(long d); virtual t_timer *copy(void) const = 0; virtual t_timer_type get_type(void) const = 0; // Get the name of the timer (for debugging purposes) virtual string get_name(void) const = 0; }; //////////////////////////////////////////////////////////////// // Transaction timer //////////////////////////////////////////////////////////////// class t_tmr_transaction : public t_timer { private: unsigned short transaction_id; t_sip_timer sip_timer; public: t_tmr_transaction(long dur, t_sip_timer tmr, unsigned short tid); void expired(void); t_timer *copy(void) const; t_timer_type get_type(void) const; unsigned short get_tid(void) const; t_sip_timer get_sip_timer(void) const; string get_name(void) const; }; //////////////////////////////////////////////////////////////// // Phone timer //////////////////////////////////////////////////////////////// class t_tmr_phone : public t_timer { private: t_phone *the_phone; t_phone_timer phone_timer; public: t_tmr_phone(long dur, t_phone_timer ptmr, t_phone *p); void expired(void); t_timer *copy(void) const; t_timer_type get_type(void) const; t_phone_timer get_phone_timer(void) const; t_phone *get_phone(void) const; string get_name(void) const; }; //////////////////////////////////////////////////////////////// // Line timer //////////////////////////////////////////////////////////////// class t_tmr_line : public t_timer { private: t_object_id line_id; t_line_timer line_timer; t_object_id dialog_id; public: t_tmr_line(long dur, t_line_timer ltmr, t_object_id lid, t_object_id d); void expired(void); t_timer *copy(void) const; t_timer_type get_type(void) const; t_line_timer get_line_timer(void) const; t_object_id get_line_id(void) const; t_object_id get_dialog_id(void) const; string get_name(void) const; }; //////////////////////////////////////////////////////////////// // Subscribe timer //////////////////////////////////////////////////////////////// class t_tmr_subscribe : public t_timer { private: t_subscribe_timer subscribe_timer; t_object_id line_id; t_object_id dialog_id; string sub_event_type; string sub_event_id; public: t_tmr_subscribe(long dur, t_subscribe_timer stmr, t_object_id lid, t_object_id d, const string &event_type, const string &event_id); void expired(void); t_timer *copy(void) const; t_timer_type get_type(void) const; t_subscribe_timer get_subscribe_timer(void) const; t_object_id get_line_id(void) const; t_object_id get_dialog_id(void) const; string get_sub_event_type(void) const; string get_sub_event_id(void) const; string get_name(void) const; }; /** Publication timer */ class t_tmr_publish : public t_timer { private: t_publish_timer publish_timer; /**< Type of timer */ string event_type; /**< Event type of publication */ public: t_tmr_publish(long dur, t_publish_timer ptmr, const string &_event_type); void expired(void); t_timer *copy(void) const; t_timer_type get_type(void) const; t_publish_timer get_publish_timer(void) const; string get_name(void) const; }; //////////////////////////////////////////////////////////////// // STUN transaction timer //////////////////////////////////////////////////////////////// class t_tmr_stun_trans : public t_timer { private: unsigned short transaction_id; t_stun_timer stun_timer; public: t_tmr_stun_trans(long dur, t_stun_timer tmr, unsigned short tid); void expired(void); t_timer *copy(void) const; t_timer_type get_type(void) const; unsigned short get_tid(void) const; t_stun_timer get_stun_timer(void) const; string get_name(void) const; }; //////////////////////////////////////////////////////////////// // Timekeeper //////////////////////////////////////////////////////////////// // A timekeeper keeps track of multiple timers per thread. // Only one single thread should call the methods of a single // timekeeper. Multiple threads using the same timekeeper will // cause havoc. class t_timekeeper { private: // List of running timers in order of timeout. As there // is only 1 real timer running on the OS. Each timer gets // a duration relative to its predecessor in the list. list timer_list; // Mutex to synchronize timekeeper actions. t_mutex mutex; // Indicate if current timer was stopped, but not removed // to prevent race conditions. Expiry of a stopped timer // will not trigger any actions. bool stopped; // Indicate if the current timer expired while the // mutex was locked. bool timer_expired; // Every method should start with locking the timekeeper // and end with unlocking. The unlocking method will check // if a timer expired during the locked state. If so, then // the expiry will be processed. void lock(void); void unlock(void); // Start the timekeeper from the thread that will handle // the SIGALRM signal. Start must be called before the // timekeeper can be used. void start(void (*timeout_handler)(int)); // Start a timer. The timer id is returned. This id is // needed to stop a timer. Pointer t should not be used // or deleted after starting. When the timer expires or // is stopped it will be deleted. void start_timer(t_timer *t); void stop_timer(t_object_id id); public: // The timeout_handler must be a signal handler for SIGALRM t_timekeeper(); ~t_timekeeper(); // Report that the current timer has expired. void report_expiry(void); // Get remaining time of a running timer. // Returns 0 if the timer is not running anymore. unsigned long get_remaining_time(t_object_id timer_id); // Main loop to be run in a separate thread void run(void); }; // Entry function for timekeeper thread void *timekeeper_main(void *arg); // Entry function of the thread waiting for SIGALRM // A dedicated thread is started to catch the SIGALRM signal and take // the appropriate action. All threads must block the SIGALRM signal. void *timekeeper_sigwait(void *arg); #endif twinkle-1.10.1/src/transaction.cpp000066400000000000000000000766701277565361200171250ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include "log.h" #include "events.h" #include "timekeeper.h" #include "transaction.h" #include "transaction_mgr.h" #include "user.h" #include "util.h" #include "audits/memman.h" extern t_event_queue *evq_sender; extern t_event_queue *evq_trans_layer; extern t_transaction_mgr *transaction_mgr; string trans_state2str(t_trans_state s) { switch(s) { case TS_NULL: return "TS_NULL"; case TS_CALLING: return "TS_CALLING"; case TS_TRYING: return "TS_TRYING"; case TS_PROCEEDING: return "TS_PROCEEDING"; case TS_COMPLETED: return "TS_COMPLETED"; case TS_CONFIRMED: return "TS_CONFIRMED"; case TS_TERMINATED: return "TS_TERMINATED"; } return "UNKNOWN"; } /////////////////////////////////////////////////////////// // RFC 3261 17 // General transaction /////////////////////////////////////////////////////////// t_mutex t_transaction::mtx_class; t_tid t_transaction::next_id = 1; t_transaction::t_transaction(t_request *r, unsigned short _tuid) { mtx_class.lock(); id = next_id++; if (next_id == 65535) next_id = 1; mtx_class.unlock(); state = TS_NULL; request = (t_request *)r->copy(); final = NULL; tuid = _tuid; } t_transaction::~t_transaction() { MEMMAN_DELETE(request); delete request; if (final != NULL) { MEMMAN_DELETE(final); delete final; } for (list::iterator i = provisional.begin(); i != provisional.end(); i++) { MEMMAN_DELETE(*i); delete *i; } } t_tid t_transaction::get_id(void) const { return id; } void t_transaction::process_provisional(t_response *r) { provisional.push_back((t_response *)r->copy()); } void t_transaction::process_final(t_response *r) { final = (t_response *)r->copy(); } void t_transaction::process_response(t_response *r) { if (r->is_provisional()) { process_provisional(r); } else { process_final(r); } } t_trans_state t_transaction::get_state(void) const { return state; } void t_transaction::set_tuid(unsigned short _tuid) { tuid = _tuid; } t_method t_transaction::get_method(void) const { return request->method; } string t_transaction::get_to_tag(void) { string tag; tag = request->hdr_to.tag; if (tag.size() > 0) return tag; if (to_tag.size() > 0) return to_tag; to_tag = random_token(TAG_LEN); return to_tag; } // RCF 3261 section 8.2.6.2 t_response *t_transaction::create_response(int code, string reason) { t_response *r; r = request->create_response(code, reason); // NOTE: 100 Trying does not establish a dialog if (code != R_100_TRYING) { r->hdr_to.set_tag(get_to_tag()); } return r; } /////////////////////////////////////////////////////////// // RFC 3261 17.1 // Client transaction /////////////////////////////////////////////////////////// t_trans_client::t_trans_client(t_request *r, const t_ip_port &ip_port, unsigned short _tuid) : t_transaction(r, _tuid), dst_ip_port(ip_port) { // Send request evq_sender->push_network(r, dst_ip_port); } // RFC 3261 17.1.3, 8.2.6.2 // Section 17.1.3 states that only the branch and CSeq method should match. // This can lead to the following problem however: // // 1) A response matches a BYE request, but has a wrong call id. // 2) As the response matches the request, the transaction finishes. // 3) Then the response is delivered to the TU which tries to match the // response to a dialog. // 4) As the call id is wrong, no match is found an the response is discarded. // 5) Now the TU keeps waiting forever for a response on the BYE // // By taking the call id into account here, this scenario is prevented. // When a call id is wrong, the BYE request will be retransmitted due to // timeouts until the transaction times out completely and a 408 is sent // to the TU. // // Same problem can occur when tags do not match, so tag is take into account // as well. So tags are take into account as well. bool t_trans_client::match(t_response *r) const { t_via &req_top_via = request->hdr_via.via_list.front(); t_via &resp_top_via = r->hdr_via.via_list.front(); return (req_top_via.branch == resp_top_via.branch && request->hdr_cseq.method == r->hdr_cseq.method && request->hdr_call_id.call_id == r->hdr_call_id.call_id && request->hdr_from.tag == r->hdr_from.tag && (request->hdr_to.tag.empty() || request->hdr_to.tag == r->hdr_to.tag)); } // An ICMP error matches a transaction when the destination IP address/port // of the packet that caused the ICMP error equals the destination // IP address/port of the transaction. Other information of the packet causing // the ICMP error is not available. // In theory when multiple transactions are open for the same destination, the // wrong transaction may process the ICMP error. In practice this should rarely // happen as the destination will be unreachable for all those transactions. // If it happens a transaction gets aborted. bool t_trans_client::match(const t_icmp_msg &icmp) const { return (dst_ip_port.ipaddr == icmp.ipaddr && dst_ip_port.port == icmp.port); } bool t_trans_client::match(const string &branch, const t_method &cseq_method) const { t_via &req_top_via = request->hdr_via.via_list.front(); return (req_top_via.branch == branch && request->hdr_cseq.method == cseq_method); } void t_trans_client::process_provisional(t_response *r) { // Set the to_tag, such that an internally genrated answer (when needed) // will have the correct tag. // An INVITE transaction may receive provisional responses with // different to-tags. Only the first to-tag will be kept and an // internally generated response will match this tag. if (!r->hdr_to.tag.empty() && to_tag.empty()) { to_tag = r->hdr_to.tag; } t_transaction::process_provisional(r); } /////////////////////////////////////////////////////////// // RFC 3261 17.1.1 // Client INVITE transaction /////////////////////////////////////////////////////////// void t_tc_invite::start_timer_A(void) { timer_A = transaction_mgr->start_timer(duration_A, TIMER_A, id); // Double duration for a next start duration_A = 2 * duration_A; } void t_tc_invite::start_timer_B(void) { timer_B = transaction_mgr->start_timer(DURATION_B, TIMER_B, id); } void t_tc_invite::start_timer_D(void) { // RFC 3261 17.1.1.2 // For reliable transport timer D must be set to zero seconds. if (dst_ip_port.transport == "udp") { timer_D = transaction_mgr->start_timer(DURATION_D, TIMER_D, id); } else { timer_D = transaction_mgr->start_timer(0, TIMER_D, id); } } void t_tc_invite::stop_timer_A(void) { if (timer_A) { transaction_mgr->stop_timer(timer_A); timer_A = 0; } } void t_tc_invite::stop_timer_B(void) { if (timer_B) { transaction_mgr->stop_timer(timer_B); timer_B = 0; } } void t_tc_invite::stop_timer_D(void) { if (timer_D) { transaction_mgr->stop_timer(timer_D); timer_D = 0; } } t_tc_invite::t_tc_invite(t_request *r, const t_ip_port &ip_port, unsigned short _tuid) : t_trans_client(r, ip_port, _tuid) { assert(r->method == INVITE); ack = NULL; duration_A = DURATION_A; state = TS_CALLING; // RFC 3261 17.1.1.2 // Start timer A for unreliable transports. if (ip_port.transport == "udp") start_timer_A(); // RFC 3261 17.1.1.2 // Start timer B for all transports start_timer_B(); timer_D = 0; } t_tc_invite::~t_tc_invite() { if (ack != NULL) { MEMMAN_DELETE(ack); delete ack; } stop_timer_A(); stop_timer_B(); stop_timer_D(); } void t_tc_invite::process_provisional(t_response *r) { assert(r->is_provisional()); switch (state) { case TS_CALLING: stop_timer_A(); stop_timer_B(); // fall through case TS_PROCEEDING: t_trans_client::process_provisional(r); state = TS_PROCEEDING; // Report to TU evq_trans_layer->push_user(r, tuid, id); break; default: // Discard provisional response in other states break; } } void t_tc_invite::process_final(t_response *r) { assert(r->is_final()); t_ip_port ip_port; switch (state) { case TS_CALLING: stop_timer_A(); stop_timer_B(); // fall through case TS_PROCEEDING: t_trans_client::process_final(r); if (r->is_success()) { state = TS_TERMINATED; } else { // RFC 3261 17.1.1.3 // construct ACK ack = new t_request(ACK); MEMMAN_NEW(ack); ack->uri = request->uri; ack->hdr_call_id = request->hdr_call_id; ack->hdr_from = request->hdr_from; ack->hdr_to = r->hdr_to; ack->hdr_via.add_via( request->hdr_via.via_list.front()); ack->hdr_cseq.set_seqnr(request->hdr_cseq.seqnr); ack->hdr_cseq.set_method(ACK); ack->hdr_route = request->hdr_route; ack->hdr_max_forwards.set_max_forwards(MAX_FORWARDS); SET_HDR_USER_AGENT(ack->hdr_user_agent) // RFC 3261 22.1 // Duplicate Authorization and Proxy-Authorization // headers from INVITE if the credentials in the // INVITE are accepted. if (r->code != R_401_UNAUTHORIZED && r->code != R_407_PROXY_AUTH_REQUIRED) { ack->hdr_authorization = request->hdr_authorization; ack->hdr_proxy_authorization = request->hdr_proxy_authorization; } // RFC 3263 4 // ACK for non-2xx SIP responses to INVITE MUST be sent t // to the same host. request->get_current_destination(ip_port); ack->set_destination(ip_port); // Send ACK evq_sender->push_network(ack, dst_ip_port); start_timer_D(); state = TS_COMPLETED; } // Report to TU evq_trans_layer->push_user(r, tuid, id); break; case TS_COMPLETED: // A failure has been received. So 2XX is not // expected anymore. Discard 2XX. if (r->is_success()) { break; } // Retransmit ACK evq_sender->push_network(ack, dst_ip_port); break; default: break; } } void t_tc_invite::process_icmp(const t_icmp_msg &icmp) { log_file->write_report("ICMP error received.", "t_tc_invite::process_icmp"); process_failure(FAIL_TRANSPORT); } void t_tc_invite::process_failure(t_failure failure) { t_response *r; switch(state) { case TS_CALLING: stop_timer_A(); stop_timer_B(); switch (failure) { case FAIL_TRANSPORT: // A transport failure indicates a kind of network problem. // So the server is not available. Generate an internal // 503 Service Unavailable repsponse to notify the TU. r = create_response(R_503_SERVICE_UNAVAILABLE); break; case FAIL_TIMEOUT: r = create_response(R_408_REQUEST_TIMEOUT); break; default: log_file->write_header("t_tc_invite::process_failure", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Unknown type of failure: "); log_file->write_raw((int)failure); log_file->write_endl(); log_file->write_footer(); r = create_response(R_400_BAD_REQUEST); break; } log_file->write_header("t_tc_invite::process_failure", LOG_NORMAL, LOG_INFO); log_file->write_raw("Transaction failed.\n\n"); log_file->write_raw("Send internal:\n"); log_file->write_raw(r->encode()); log_file->write_footer(); evq_trans_layer->push_user(r, tuid, id); MEMMAN_DELETE(r); delete r; state = TS_TERMINATED; break; default: // In other states a response has been received already, // so this failure seems to be a mismatch. Discard. break; } } void t_tc_invite::timeout(t_sip_timer t) { t_response *r; assert (t == TIMER_A || t == TIMER_B || t == TIMER_D); switch (state) { case TS_CALLING: switch (t) { case TIMER_A: // Resend request evq_sender->push_network(request, dst_ip_port); start_timer_A(); break; case TIMER_B: stop_timer_A(); timer_B = 0; // Report timer expiry to TU r = create_response(R_408_REQUEST_TIMEOUT); log_file->write_header("t_tc_invite::timeout", LOG_NORMAL, LOG_INFO); log_file->write_raw("Timer B expired.\n\n"); log_file->write_raw("Send internal:\n"); log_file->write_raw(r->encode()); log_file->write_footer(); evq_trans_layer->push_user(r, tuid, id); MEMMAN_DELETE(r); delete r; state = TS_TERMINATED; break; default: // Ignore expiry of other timers. // Other timers should have been stopped. break; } break; case TS_COMPLETED: switch (t) { case TIMER_D: timer_D = 0; state = TS_TERMINATED; break; default: // Ignore expiry of other timers. // Other timers should have been stopped. break; } break; default: // Ignore timer expiries in other states // Other timers should have been stopped. break; } } void t_tc_invite::abort(void) { t_response *r; switch (state) { case TS_PROCEEDING: r = create_response(R_408_REQUEST_TIMEOUT, "Request Aborted"); log_file->write_header("t_tc_invite::abort", LOG_NORMAL, LOG_INFO); log_file->write_raw("Invite transaction aborted.\n\n"); log_file->write_raw("Send internal:\n"); log_file->write_raw(r->encode()); log_file->write_footer(); evq_trans_layer->push_user(r, tuid, id); MEMMAN_DELETE(r); delete r; state = TS_TERMINATED; break; default: // Ignore abortion in other states. // In other states the request can be terminated in // a normal way. break; } } /////////////////////////////////////////////////////////// // RFC 3261 17.1.2 // Client non-INVITE transaction /////////////////////////////////////////////////////////// void t_tc_non_invite::start_timer_E(void) { if (state == TS_PROCEEDING) duration_E = DURATION_T2; timer_E = transaction_mgr->start_timer(duration_E, TIMER_E, id); duration_E = 2 * duration_E; if (duration_E > DURATION_T2) duration_E = DURATION_T2; } void t_tc_non_invite::start_timer_F(void) { timer_F = transaction_mgr->start_timer(DURATION_F, TIMER_F, id); } void t_tc_non_invite::start_timer_K(void) { // RFC 3261 17.1.2.2 // For reliable transports set timer K to zero seconds. if (dst_ip_port.transport == "udp") { timer_K = transaction_mgr->start_timer(DURATION_K, TIMER_K, id); } else { timer_K = transaction_mgr->start_timer(0, TIMER_K, id); } } void t_tc_non_invite::stop_timer_E(void) { if (timer_E) { transaction_mgr->stop_timer(timer_E); timer_E = 0; } } void t_tc_non_invite::stop_timer_F(void) { if (timer_F) { transaction_mgr->stop_timer(timer_F); timer_F = 0; } } void t_tc_non_invite::stop_timer_K(void) { if (timer_K) { transaction_mgr->stop_timer(timer_K); timer_K = 0; } } t_tc_non_invite::t_tc_non_invite(t_request *r, const t_ip_port &ip_port, unsigned short _tuid) : t_trans_client(r, ip_port, _tuid) { assert(r->method != INVITE); state = TS_TRYING; duration_E = DURATION_E; // RFC 3261 17.1.2.2 // Start timer E for unreliable transports. if (ip_port.transport == "udp") start_timer_E(); // RFC 3261 17.1.2.2 // Start timer F for all transports. start_timer_F(); timer_K = 0; } t_tc_non_invite::~t_tc_non_invite() { stop_timer_E(); stop_timer_F(); stop_timer_K(); } void t_tc_non_invite::process_provisional(t_response *r) { assert(r->is_provisional()); switch (state) { case TS_TRYING: case TS_PROCEEDING: t_trans_client::process_provisional(r); state = TS_PROCEEDING; // Report to TU evq_trans_layer->push_user(r, tuid, id); break; default: // Discard provisional response in other states break; } } void t_tc_non_invite::process_final(t_response *r) { assert(r->is_final()); switch (state) { case TS_TRYING: case TS_PROCEEDING: t_trans_client::process_final(r); stop_timer_E(); stop_timer_F(); // Report to TU evq_trans_layer->push_user(r, tuid, id); start_timer_K(); state = TS_COMPLETED; break; case TS_COMPLETED: // The received response is a retransmission. // AS the final response is already received this // retransmission can be discarded. // fall through default: break; } } void t_tc_non_invite::process_icmp(const t_icmp_msg &icmp) { log_file->write_report("ICMP error received.", "t_tc_non_invite::process_icmp"); process_failure(FAIL_TRANSPORT); } void t_tc_non_invite::process_failure(t_failure failure) { t_response *r; switch(state) { case TS_TRYING: stop_timer_E(); stop_timer_F(); switch (failure) { case FAIL_TRANSPORT: // A transport failure indicates a kind of network problem. // So the server is not available. Generate an internal // 503 Service Unavailable repsponse to notify the TU. r = create_response(R_503_SERVICE_UNAVAILABLE); break; case FAIL_TIMEOUT: r = create_response(R_408_REQUEST_TIMEOUT); break; default: log_file->write_header("t_tc_non_invite::process_failure", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Unknown type of failure: "); log_file->write_raw((int)failure); log_file->write_endl(); log_file->write_footer(); r = create_response(R_400_BAD_REQUEST); break; } log_file->write_header("t_tc_non_invite::process_failure", LOG_NORMAL, LOG_INFO); log_file->write_raw("Transaction failed.\n\n"); log_file->write_raw("Send internal:\n"); log_file->write_raw(r->encode()); log_file->write_footer(); evq_trans_layer->push_user(r, tuid, id); MEMMAN_DELETE(r); delete r; state = TS_TERMINATED; break; default: // In other states a response has been received already, // so this failure seems to be a mismatch. Discard. break; } } void t_tc_non_invite::timeout(t_sip_timer t) { t_response *r; assert (t == TIMER_E || t == TIMER_F || t == TIMER_K); switch (state) { case TS_TRYING: case TS_PROCEEDING: switch (t) { case TIMER_E: // Resend request evq_sender->push_network(request, dst_ip_port); start_timer_E(); break; case TIMER_F: timer_F = 0; stop_timer_E(); // Report timer expiry to TU r = create_response(R_408_REQUEST_TIMEOUT); log_file->write_header("t_tc_non_invite::timeout", LOG_NORMAL, LOG_INFO); log_file->write_raw("Timer F expired.\n\n"); log_file->write_raw("Send internal:\n"); log_file->write_raw(r->encode()); log_file->write_footer(); evq_trans_layer->push_user(r, tuid, id); MEMMAN_DELETE(r); delete r; state = TS_TERMINATED; break; default: // Ignore expiry of other timers. // Other timers should have been stopped. break; } break; case TS_COMPLETED: switch (t) { case TIMER_K: timer_K = 0; state = TS_TERMINATED; break; default: // Ignore expiry of other timers. // Other timers should have been stopped. break; } default: // Ignore timer expiries in other states // Other timers should have been stopped. break; } } void t_tc_non_invite::abort(void) { t_response *r; switch (state) { case TS_TRYING: case TS_PROCEEDING: stop_timer_E(); stop_timer_F(); r = create_response(R_408_REQUEST_TIMEOUT, "Request Aborted"); log_file->write_header("t_tc_non_invite::abort", LOG_NORMAL, LOG_INFO); log_file->write_raw("Non-invite transaction aborted.\n\n"); log_file->write_raw("Send internal:\n"); log_file->write_raw(r->encode()); log_file->write_footer(); evq_trans_layer->push_user(r, tuid, id); MEMMAN_DELETE(r); delete r; state = TS_TERMINATED; break; default: // Ignore abortion in other states. // In other states the request can be terminated in // a normal way. break; } } /////////////////////////////////////////////////////////// // RFC 3261 17.2 // Server transaction /////////////////////////////////////////////////////////// t_trans_server::t_trans_server(t_request *r, unsigned short _tuid) : t_transaction(r, _tuid), resp_100_trying_sent(false) { t_trans_server *t; t_tid tid_cancel = 0; // Report to TU if (request->method == CANCEL) { t = transaction_mgr->find_cancel_target(r); if (t) tid_cancel = t->get_id(); evq_trans_layer->push_user_cancel(r, tuid, id, tid_cancel); } else { evq_trans_layer->push_user(r, tuid, id); } } void t_trans_server::process_provisional(t_response *r) { t_ip_port ip_port; if (r->code == R_100_TRYING && resp_100_trying_sent) { // Send 100 Trying only once return; } t_transaction::process_provisional(r); r->get_destination(ip_port); if (ip_port.ipaddr == 0) { // The response cannot be sent. state = TS_TERMINATED; // Report failure to TU evq_trans_layer->push_failure(FAIL_TRANSPORT, id); } else { // Send response evq_sender->push_network(r, ip_port); if (r->code == R_100_TRYING) { resp_100_trying_sent = true; } } } void t_trans_server::process_final(t_response *r) { t_ip_port ip_port; t_transaction::process_final(r); r->get_destination(ip_port); if (ip_port.ipaddr == 0) { // The response cannot be sent. state = TS_TERMINATED; // Report failure to TU evq_trans_layer->push_failure(FAIL_TRANSPORT, id); } else { // Send response evq_sender->push_network(r, ip_port); } } void t_trans_server::process_retransmission(void) { // nothing to do } // RFC 3261 17.2.3 // NOTE: retransmission of an incoming INVITE for which a 2XX response // has been sent already is checked by the TU. // see dialog::is_invite_retrans bool t_trans_server::match(t_request *r, bool cancel) const { t_via &orig_top_via = request->hdr_via.via_list.front(); t_via &recv_top_via = r->hdr_via.via_list.front(); if (recv_top_via.rfc3261_compliant()) { if (orig_top_via.branch != recv_top_via.branch) return false; if (orig_top_via.host != recv_top_via.host) return false; if (orig_top_via.port != recv_top_via.port) return false; switch(r->method) { case ACK: // return (request->hdr_cseq.method == INVITE); return (request->method == INVITE); break; case CANCEL: if (!cancel) { // return (request->hdr_cseq.method == // r->hdr_cseq.method); return (request->method == r->method); } // The target of CANCEL cannot be a CANCEL request // return (request->hdr_cseq.method != CANCEL); return (request->method != CANCEL); break; default: // return (request->hdr_cseq.method == // r->hdr_cseq.method); return (request->method == r->method); break; } } // Matching rules for backward compatibiliy with RFC 2543 // TODO: verify rules for matching via headers switch (r->method) { case INVITE: return (request->method == INVITE && request->uri.sip_match(r->uri) && request->hdr_to.tag == r->hdr_to.tag && request->hdr_from.tag == r->hdr_from.tag && request->hdr_call_id.call_id == r->hdr_call_id.call_id && request->hdr_cseq.seqnr == r->hdr_cseq.seqnr && orig_top_via.host == recv_top_via.host && orig_top_via.port == recv_top_via.port); break; case ACK: return (request->method == INVITE && request->uri.sip_match(r->uri) && request->hdr_from.tag == r->hdr_from.tag && request->hdr_call_id.call_id == r->hdr_call_id.call_id && request->hdr_cseq.seqnr == r->hdr_cseq.seqnr && orig_top_via.host == recv_top_via.host && orig_top_via.port == recv_top_via.port && final != NULL && final->hdr_to.tag == r->hdr_to.tag); break; case CANCEL: if (cancel) { return (request->uri.sip_match(r->uri) && request->hdr_from.tag == r->hdr_from.tag && request->hdr_call_id.call_id == r->hdr_call_id.call_id && request->hdr_cseq.seqnr == r->hdr_cseq.seqnr && request->hdr_cseq.method != CANCEL && orig_top_via.host == recv_top_via.host && orig_top_via.port == recv_top_via.port); } // fall through default: return (request->uri.sip_match(r->uri) && request->hdr_from.tag == r->hdr_from.tag && request->hdr_call_id.call_id == r->hdr_call_id.call_id && request->hdr_cseq == r->hdr_cseq && orig_top_via.host == recv_top_via.host && orig_top_via.port == recv_top_via.port); break; } // Should not get here return false; } bool t_trans_server::match(t_request *r) const { return match(r, false); } bool t_trans_server::match_cancel(t_request *r) const { assert(r->method == CANCEL); return match(r, true); } /////////////////////////////////////////////////////////// // RFC 3261 17.2.1 // Server INVITE transaction /////////////////////////////////////////////////////////// void t_ts_invite::start_timer_G(void) { timer_G = transaction_mgr->start_timer(duration_G, TIMER_G, id); duration_G = 2 * duration_G; if (duration_G > DURATION_T2) duration_G = DURATION_T2; } void t_ts_invite::start_timer_H(void) { timer_H = transaction_mgr->start_timer(DURATION_H, TIMER_H, id); } void t_ts_invite::start_timer_I(void) { // RFC 17.2.1 // Set timer I to T4 seconds for unreliable transports and to 0 for // reliable transports. if (request->src_ip_port.transport == "udp") { timer_I = transaction_mgr->start_timer(DURATION_I, TIMER_I, id); } else { timer_I = transaction_mgr->start_timer(0, TIMER_I, id); } } void t_ts_invite::stop_timer_G(void) { if (timer_G) { transaction_mgr->stop_timer(timer_G); timer_G = 0; } } void t_ts_invite::stop_timer_H(void) { if (timer_H) { transaction_mgr->stop_timer(timer_H); timer_H = 0; } } void t_ts_invite::stop_timer_I(void) { if (timer_I) { transaction_mgr->stop_timer(timer_I); timer_I = 0; } } t_ts_invite::t_ts_invite(t_request *r, unsigned short _tuid) : t_trans_server(r, _tuid) { assert(r->method == INVITE); state = TS_PROCEEDING; ack = NULL; timer_G = 0; timer_H = 0; timer_I = 0; duration_G = DURATION_G; } t_ts_invite::~t_ts_invite() { if (ack != NULL) { MEMMAN_DELETE(ack); delete ack; } stop_timer_G(); stop_timer_H(); stop_timer_I(); } void t_ts_invite::process_provisional(t_response *r) { assert(r->is_provisional()); switch (state) { case TS_PROCEEDING: t_trans_server::process_provisional(r); break; default: // TU should not send a provisional response // in other states. assert(false); break; } } void t_ts_invite::process_final(t_response *r) { assert(r->is_final()); switch (state) { case TS_PROCEEDING: t_trans_server::process_final(r); if (r->is_success()) { state = TS_TERMINATED; } else { // RFC 3261 17.2.1 // Start timer G for unreliable transports. if (request->src_ip_port.transport == "udp") { start_timer_G(); } // RFC 3261 17.2.1 // Start timer H for all transports start_timer_H(); state = TS_COMPLETED; } break; default: // No final responses are expected anymore. Discard. break; } } void t_ts_invite::process_retransmission(void) { t_ip_port ip_port; switch (state) { case TS_PROCEEDING: // Retransmit the latest provisional response (if present) t_trans_server::process_retransmission(); if (provisional.size() > 0) { t_response *r = provisional.back(); r->get_destination(ip_port); if (ip_port.ipaddr == 0) { // The response cannot be sent. state = TS_TERMINATED; // Report failure to TU evq_trans_layer->push_failure( FAIL_TRANSPORT, id); } else { // Send response evq_sender->push_network(r, ip_port); } } break; case TS_COMPLETED: // Retransmit the final response t_trans_server::process_retransmission(); final->get_destination(ip_port); if (ip_port.ipaddr == 0) { // The response cannot be sent. state = TS_TERMINATED; // Report failure to TU evq_trans_layer->push_failure(FAIL_TRANSPORT, id); } else { // Send response evq_sender->push_network(final, ip_port); } break; default: // Retransmissions should not happen in other states. // Discard. break; } } void t_ts_invite::timeout(t_sip_timer t) { t_ip_port ip_port; assert(t == TIMER_G || t == TIMER_I || t == TIMER_H); switch (state) { case TS_COMPLETED: switch (t) { case TIMER_G: timer_G = 0; // Retransmit the final response final->get_destination(ip_port); if (ip_port.ipaddr == 0) { // The response cannot be sent. stop_timer_H(); state = TS_TERMINATED; // Report failure to TU evq_trans_layer->push_failure( FAIL_TRANSPORT, id); } else { // Send response evq_sender->push_network(final, ip_port); start_timer_G(); } break; case TIMER_H: timer_H = 0; stop_timer_G(); state = TS_TERMINATED; // Report timer expiry to TU evq_trans_layer->push_failure(FAIL_TIMEOUT, id); break; default: // No other timers should be running. Discard. break; } break; case TS_CONFIRMED: switch (t) { case TIMER_I: timer_I = 0; state = TS_TERMINATED; break; default: // No other timers should be running. Discard. break; } default: // In other states no timers should be running. break; } } void t_ts_invite::acknowledge(t_request *ack_request) { assert(ack_request->method == ACK); switch (state) { case TS_COMPLETED: ack = (t_request *)ack_request->copy(); stop_timer_G(); stop_timer_H(); start_timer_I(); state = TS_CONFIRMED; // Report TU // ACK should not be reported to TU for non-2xx // evq_trans_layer->push_user(ack_request, tuid, id); break; default: // ACK is not expected in other states. Discard; break; } } /////////////////////////////////////////////////////////// // RFC 3261 17.2.2 // Server non-INVITE transaction /////////////////////////////////////////////////////////// void t_ts_non_invite::start_timer_J(void) { // RFC 3261 17.2.2 // For unreliable transports set timer J to 64*T1, for reliable // transports set it to 0. if (request->src_ip_port.transport == "udp") { timer_J = transaction_mgr->start_timer(DURATION_J, TIMER_J, id); } else { timer_J = transaction_mgr->start_timer(0, TIMER_J, id); } } void t_ts_non_invite::stop_timer_J(void) { if (timer_J) { transaction_mgr->stop_timer(timer_J); timer_J = 0; } } t_ts_non_invite::t_ts_non_invite(t_request *r, unsigned short _tuid) : t_trans_server(r, _tuid) { assert(r->method != INVITE); timer_J = 0; state = TS_TRYING; } t_ts_non_invite::~t_ts_non_invite() { stop_timer_J(); } void t_ts_non_invite::process_provisional(t_response *r) { assert(r->is_provisional()); switch (state) { case TS_TRYING: case TS_PROCEEDING: t_trans_server::process_provisional(r); state = TS_PROCEEDING; break; default: // TU should not send a provisional response // in other states. assert(false); break; } } void t_ts_non_invite::process_final(t_response *r) { assert(r->is_final()); switch (state) { case TS_TRYING: case TS_PROCEEDING: t_trans_server::process_final(r); start_timer_J(); state = TS_COMPLETED; break; default: // No final responses are expected anymore. Discard. break; } } void t_ts_non_invite::process_retransmission(void) { t_ip_port ip_port; t_response *r; switch (state) { case TS_PROCEEDING: // Retransmit the latest provisional response t_trans_server::process_retransmission(); r = provisional.back(); r->get_destination(ip_port); if (ip_port.ipaddr == 0) { // The response cannot be sent. state = TS_TERMINATED; // Report failure to TU evq_trans_layer->push_failure(FAIL_TRANSPORT, id); } else { // Send response evq_sender->push_network(r, ip_port); } break; case TS_COMPLETED: // Retransmit the final response t_trans_server::process_retransmission(); final->get_destination(ip_port); if (ip_port.ipaddr == 0) { // The response cannot be sent. stop_timer_J(); state = TS_TERMINATED; // Report failure to TU evq_trans_layer->push_failure(FAIL_TRANSPORT, id); } else { // Send response evq_sender->push_network(final, ip_port); } break; default: // Retransmissions should not happen in other states. // Discard. break; } } void t_ts_non_invite::timeout(t_sip_timer t) { assert (t == TIMER_J); switch (state) { case TS_COMPLETED: switch (t) { case TIMER_J: timer_J = 0; state = TS_TERMINATED; break; default: break; } default: break; } } twinkle-1.10.1/src/transaction.h000066400000000000000000000234601277565361200165570ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _TRANSACTION_H #define _TRANSACTION_H #include #include "protocol.h" #include "parser/request.h" #include "parser/response.h" #include "sockets/socket.h" #include "threads/mutex.h" using namespace std; typedef unsigned short t_tid; ///////////////////////////////////////////////////////////// // Transaction state (see RFC 3261 17) ///////////////////////////////////////////////////////////// enum t_trans_state { TS_NULL, // non-state used for initialization TS_CALLING, TS_TRYING, TS_PROCEEDING, TS_COMPLETED, TS_CONFIRMED, TS_TERMINATED, }; string trans_state2str(t_trans_state s); ///////////////////////////////////////////////////////////// // General transaction ///////////////////////////////////////////////////////////// // // Concurrent creation of transactions is not allowed. If this // is needed then updates to static members need to be // synchronized with a mutex. // All transactions are created by the transaction manager. This // should not be changed as transactions start timers and all timers // must be started from a single thread. class t_transaction { private: static t_mutex mtx_class; // protect static members static t_tid next_id; // next id to be issued protected: t_tid id; // transaction id unsigned short tuid; // TU id t_trans_state state; string to_tag; // tag for to-header public: // Request that created the transaction t_request *request; t_tid get_id(void) const; // Provisional responses in order of arrival/sending list provisional; // Final response for the transaction t_response *final; // The transaction will keep a copy of the request t_transaction(t_request *r, unsigned short _tuid); // All request and response pointers contained by the // request will be deleted. virtual ~t_transaction(); // Process a provisional repsonse // Transaction will keep a copy of the response virtual void process_provisional(t_response *r); // Process a final response // Transaction will keep a copy of the response virtual void process_final(t_response *r); // Process a response virtual void process_response(t_response *r); // Process timer expiry virtual void timeout(t_sip_timer t) = 0; // Get state of the transaction t_trans_state get_state(void) const; // Set TU ID void set_tuid(unsigned short _tuid); // Get type of request t_method get_method(void) const; // Get tag for to-header string get_to_tag(void); // Create response according to general rules t_response *create_response(int code, string reason = ""); }; ///////////////////////////////////////////////////////////// // Client transaction ///////////////////////////////////////////////////////////// class t_trans_client : public t_transaction { protected: /** Destination for request. */ t_ip_port dst_ip_port; public: /** * Create transaction and send request to destination. * @param r [in] Request creating the transaction. * @param ip_port [in] Destination of the request. * @param _tuid [in] Transaction user id assigned to this transaction. */ t_trans_client(t_request *r, const t_ip_port &ip_port, unsigned short _tuid); /** * Match a response with a transaction. * @param r [in] The response to match. * @return true if the response matches the transaction. */ bool match(t_response *r) const; /** * @param icmp [in] ICMP message to match. * @return true if the ICMP error matches the transaction */ bool match(const t_icmp_msg &icmp) const; /** * Match transaction with a branch and CSeq method value. * @param branch [in] Branch to match. * @param cseq_method [in] CSeq method to match. * @return true if transaction matches, otherwise false. */ bool match(const string &branch, const t_method &cseq_method) const; virtual void process_provisional(t_response *r); /** * Process ICMP errors. * @param icmp [in] ICMP message. */ virtual void process_icmp(const t_icmp_msg &icmp) = 0; /** * Process failures. * @param failure [in] Type of failure. */ virtual void process_failure(t_failure failure) = 0; /** * Abort a transaction. * This will send a 408 response internally to finish the transaction. */ virtual void abort(void) = 0; }; ///////////////////////////////////////////////////////////// // Client INVITE transaction ///////////////////////////////////////////////////////////// class t_tc_invite : public t_trans_client { private: // Timers unsigned short timer_A; unsigned short timer_B; unsigned short timer_D; // Duration of next timer A in msec long duration_A; void start_timer_A(void); void start_timer_B(void); void start_timer_D(void); void stop_timer_A(void); void stop_timer_B(void); void stop_timer_D(void); public: t_request *ack; // ACK request // Create transaction and send request to destination // Start timer A and timer B t_tc_invite(t_request *r, const t_ip_port &ip_port, unsigned short _tuid); virtual ~t_tc_invite(); // Process a provisional repsonse // Stop timer A void process_provisional(t_response *r); // Process a final response // Stop timer B. // Start timer D (for non-2xx final). void process_final(t_response *r); void process_icmp(const t_icmp_msg &icmp); void process_failure(t_failure failure); void timeout(t_sip_timer t); void abort(void); }; ///////////////////////////////////////////////////////////// // Client non-INVITE transaction ///////////////////////////////////////////////////////////// class t_tc_non_invite : public t_trans_client { private: // Timers unsigned short timer_E; unsigned short timer_F; unsigned short timer_K; // Duration of next timer E in msec long duration_E; void start_timer_E(void); void start_timer_F(void); void start_timer_K(void); void stop_timer_E(void); void stop_timer_F(void); void stop_timer_K(void); public: // Create transaction and send request to destination // Stop timer E and timer F t_tc_non_invite(t_request *r, const t_ip_port &ip_port, unsigned short _tuid); virtual ~t_tc_non_invite(); // Process a provisional repsonse void process_provisional(t_response *r); // Process final response // Stop timer E and F. Start timer K. void process_final(t_response *r); void process_icmp(const t_icmp_msg &icmp); void process_failure(t_failure failure); void timeout(t_sip_timer t); void abort(void); }; ///////////////////////////////////////////////////////////// // Server transaction ///////////////////////////////////////////////////////////// class t_trans_server : public t_transaction { private: // Match a the transaction to a request. Argument // If cancel==true then the target for a CANCEL // is matched. // If cancel==false then the request itself is matched, // eg. retransmission or ACK to INVITE matching bool match(t_request *r, bool cancel) const; // Indicates if a 100 Trying has already been sent. // A 100 Trying should only be sent once. // The reason for sending a 100 Trying is to indicate that // the request has been received but that processing will // take some time. // Based on the tasks to perform several parts of the transaction // user can decide independently to send a 100 Trying. This // flag assures that only one 100 Trying will be sent out // though. bool resp_100_trying_sent; public: t_trans_server(t_request *r, unsigned short _tuid); // Process a provisional repsonse // Send provisional response void process_provisional(t_response *r); // Process a final response // Send the final response void process_final(t_response *r); // Process a received retransmission of the request virtual void process_retransmission(void); // Returns true if request matches transaction bool match(t_request *r) const; // Returns true if the transaction is the target of CANCEL bool match_cancel(t_request *r) const; }; ///////////////////////////////////////////////////////////// // Server INIVITE transaction ///////////////////////////////////////////////////////////// class t_ts_invite : public t_trans_server { private: // Timers unsigned short timer_G; unsigned short timer_H; unsigned short timer_I; // Duration of next timer G in msec long duration_G; void start_timer_G(void); void start_timer_H(void); void start_timer_I(void); void stop_timer_G(void); void stop_timer_H(void); void stop_timer_I(void); public: t_request *ack; // ACK request t_ts_invite(t_request *r, unsigned short _tuid); virtual ~t_ts_invite(); void process_provisional(t_response *r); void process_final(t_response *r); void process_retransmission(void); void timeout(t_sip_timer t); // Transaction will keep a copy of the ACK. void acknowledge(t_request *ack_request); }; ///////////////////////////////////////////////////////////// // Server non-INVITE transaction ///////////////////////////////////////////////////////////// class t_ts_non_invite : public t_trans_server { private: // Timers unsigned short timer_J; void start_timer_J(void); void stop_timer_J(void); public: t_ts_non_invite(t_request *r, unsigned short _tuid); virtual ~t_ts_non_invite(); void process_provisional(t_response *r); void process_final(t_response *r); void process_retransmission(void); void timeout(t_sip_timer t); }; #endif twinkle-1.10.1/src/transaction_layer.cpp000066400000000000000000000171061277565361200203060ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include "events.h" #include "transaction_layer.h" #include "userintf.h" #include "util.h" #include "audits/memman.h" extern t_event_queue *evq_trans_mgr; extern t_event_queue *evq_trans_layer; extern bool end_app; void t_transaction_layer::recvd_response(t_response *r, t_tuid tuid, t_tid tid) { lock(); switch(r->get_class()) { case R_1XX: recvd_provisional(r, tuid, tid); break; case R_2XX: recvd_success(r, tuid, tid); break; case R_3XX: recvd_redirect(r, tuid, tid); break; case R_4XX: recvd_client_error(r, tuid, tid); break; case R_5XX: recvd_server_error(r, tuid, tid); break; case R_6XX: recvd_global_error(r, tuid, tid); break; default: assert(false); break; } post_process_response(r, tuid, tid); unlock(); } void t_transaction_layer::recvd_request(t_request *r, t_tid tid, t_tid tid_cancel_target) { bool fatal; string reason; t_response *resp; lock(); // Return a 400 response if the SIP headers are wrong if (!r->is_valid(fatal, reason)) { resp = r->create_response(R_400_BAD_REQUEST, reason); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; unlock(); return; } // Return a 400 response if the SIP body contained a parse error if (r->body && r->body->invalid) { resp = r->create_response(R_400_BAD_REQUEST, "Invalid SIP body."); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; unlock(); return; } // If a message exceeded the maximum message size, than the body // is not parsed by the listener. if (r->hdr_content_length.is_populated() && r->hdr_content_length.length > 0 && !r->body) { resp = r->create_response(R_513_MESSAGE_TOO_LARGE); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; unlock(); return; } // RFC 3261 8.2.3 // Return a 415 response if content encoding is not supported if (r->body && r->hdr_content_encoding.is_populated()) { for (list::iterator it = r->hdr_content_encoding.coding_list.begin(); it != r->hdr_content_encoding.coding_list.end(); ++it) { if (!CONTENT_ENCODING_SUPPORTED(it->content_coding)) { resp = r->create_response(R_415_UNSUPPORTED_MEDIA_TYPE); SET_HDR_ACCEPT_ENCODING(resp->hdr_accept_encoding); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; unlock(); return; } } } // Check if URI scheme is supported if (r->uri.get_scheme() != "sip") { resp = r->create_response(R_416_UNSUPPORTED_URI_SCHEME); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; unlock(); return; } switch(r->method) { case INVITE: recvd_invite(r, tid); break; case ACK: recvd_ack(r, tid); break; case CANCEL: recvd_cancel(r, tid, tid_cancel_target); break; case BYE: recvd_bye(r, tid); break; case OPTIONS: recvd_options(r, tid); break; case REGISTER: recvd_register(r, tid); break; case PRACK: recvd_prack(r, tid); break; case SUBSCRIBE: recvd_subscribe(r, tid); break; case NOTIFY: recvd_notify(r, tid); break; case REFER: recvd_refer(r, tid); break; case INFO: recvd_info(r, tid); break; case MESSAGE: recvd_message(r, tid); break; default: resp = r->create_response(R_501_NOT_IMPLEMENTED); send_response(resp, 0, tid); MEMMAN_DELETE(resp); delete resp; break; } post_process_request(r, tid, tid_cancel_target); unlock(); } void t_transaction_layer::recvd_async_response(t_event_async_response *event) { lock(); switch (event->get_response_type()) { case t_event_async_response::RESP_REFER_PERMISSION: recvd_refer_permission(event->get_bool_response()); break; default: // Ignore other responses break; } unlock(); } void t_transaction_layer::send_request(t_user *user_config, t_request *r, t_tuid tuid) { evq_trans_mgr->push_user(user_config, (t_sip_message *)r, tuid, 0); } void t_transaction_layer::send_request(t_user *user_config, StunMessage *r, t_tuid tuid) { // The transaction manager will determine the destination IP and port, // so they can be left to zero in the event. evq_trans_mgr->push_stun_request(user_config, r, TYPE_STUN_SIP, tuid, 0, 0, 0); } void t_transaction_layer::send_response(t_response *r, t_tuid tuid, t_tid tid) { evq_trans_mgr->push_user((t_sip_message *)r, tuid, tid); } void t_transaction_layer::run(void) { t_event *event; t_event_user *ev_user; t_event_timeout *ev_timeout; t_event_failure *ev_failure; t_event_stun_response *ev_stun_resp; t_event_async_response *ev_async_resp; t_event_broken_connection *ev_broken_connection; t_sip_message *msg; StunMessage *stun_msg; t_tid tid; t_tid tid_cancel; t_tuid tuid; bool quit = false; while (!quit) { event = evq_trans_layer->pop(); switch (event->get_type()) { case EV_USER: ev_user = (t_event_user *)event; tid = ev_user->get_tid(); tuid = ev_user->get_tuid(); tid_cancel = ev_user->get_tid_cancel_target(); msg = ev_user->get_msg(); switch(msg->get_type()) { case MSG_REQUEST: recvd_request((t_request *)msg, tid, tid_cancel); break; case MSG_RESPONSE: recvd_response((t_response *)msg, tuid, tid); break; default: assert(false); break; } break; case EV_TIMEOUT: ev_timeout = dynamic_cast(event); handle_event_timeout(ev_timeout); break; case EV_FAILURE: ev_failure = (t_event_failure *)event; tid = ev_failure->get_tid(); lock(); failure(ev_failure->get_failure(), tid); unlock(); break; case EV_STUN_RESPONSE: ev_stun_resp = (t_event_stun_response *)event; tid = ev_stun_resp->get_tid(); tuid = ev_stun_resp->get_tuid(); stun_msg = ev_stun_resp->get_msg(); recvd_stun_resp(stun_msg, tuid, tid); break; case EV_ASYNC_RESPONSE: ev_async_resp = dynamic_cast(event); recvd_async_response(ev_async_resp); break; case EV_BROKEN_CONNECTION: ev_broken_connection = dynamic_cast(event); handle_broken_connection(ev_broken_connection); break; case EV_QUIT: quit = true; break; default: // other types of event are not expected assert(false); break; } MEMMAN_DELETE(event); delete event; } } void t_transaction_layer::lock(void) const { // Prohibited threads may not lock the transaction layer assert(!is_prohibited_thread()); // The user interface and transaction layer threads both call // functions on the transaction layer. By locking the UI mutex // first, a deadlock can never occur as the UI also takes the // UI lock first and then the transaction layer lock. // During shutdown of Twinkle the GUI has exited already and // a lock on an exited QApplication causes a segmentation fault. // Therefore the lock on the UI should not be taken during shutdown. if (!end_app) ui->lock(); tl_mutex.lock(); } void t_transaction_layer::unlock(void) const { tl_mutex.unlock(); if (!end_app) ui->unlock(); } twinkle-1.10.1/src/transaction_layer.h000066400000000000000000000100721277565361200177460ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _TRANSACTION_LAYER_H #define _TRANSACTION_LAYER_H #include "events.h" #include "prohibit_thread.h" #include "transaction.h" #include "parser/request.h" #include "parser/response.h" #include "stun/stun.h" #include "threads/mutex.h" typedef unsigned short t_tuid; class t_transaction_layer : public i_prohibit_thread { private: // Mutex to guarantee that only 1 thread at a time is // accessing the transaction layer. mutable t_recursive_mutex tl_mutex; void recvd_response(t_response *r, t_tuid tuid, t_tid tid); void recvd_request(t_request *r, t_tid tid, t_tid tid_cancel_target); void recvd_async_response(t_event_async_response *event); protected: // Client event handlers // After returning from this function, the response pointer // will be deleted. virtual void recvd_provisional(t_response *r, t_tuid tuid, t_tid tid) = 0; virtual void recvd_success(t_response *r, t_tuid tuid, t_tid tid) = 0; virtual void recvd_redirect(t_response *r, t_tuid tuid, t_tid tid) = 0; virtual void recvd_client_error(t_response *r, t_tuid tuid, t_tid tid) = 0; virtual void recvd_server_error(t_response *r, t_tuid tuid, t_tid tid) = 0; virtual void recvd_global_error(t_response *r, t_tuid tuid, t_tid tid) = 0; // General post processing for all responses virtual void post_process_response(t_response *r, t_tuid tuid, t_tid tid) = 0; // Server event handlers // After returning from this function, the request pointer // will be deleted. virtual void recvd_invite(t_request *r, t_tid tid) = 0; virtual void recvd_ack(t_request *r, t_tid tid) = 0; virtual void recvd_cancel(t_request *r, t_tid cancel_tid, t_tid target_tid) = 0; virtual void recvd_bye(t_request *r, t_tid tid) = 0; virtual void recvd_options(t_request *r, t_tid tid) = 0; virtual void recvd_register(t_request *r, t_tid tid) = 0; virtual void recvd_prack(t_request *r, t_tid tid) = 0; virtual void recvd_subscribe(t_request *r, t_tid tid) = 0; virtual void recvd_notify(t_request *r, t_tid tid) = 0; virtual void recvd_refer(t_request *r, t_tid tid) = 0; virtual void recvd_info(t_request *r, t_tid tid) = 0; virtual void recvd_message(t_request *r, t_tid tid) = 0; // General post processing for all requests virtual void post_process_request(t_request *r, t_tid cancel_tid, t_tid target_tid) = 0; // The transaction failed and is aborted virtual void failure(t_failure failure, t_tid tid) = 0; // STUN event handler virtual void recvd_stun_resp(StunMessage *r, t_tuid tuid, t_tid tid) = 0; // The user has granted or rejected an incoming REFER request. virtual void recvd_refer_permission(bool permission) = 0; /** * Handle timeout event. * @param e [in] Timeout event. */ virtual void handle_event_timeout(t_event_timeout *e) = 0; /** * Handle broken connection event. * @param e [in] Broken connection event. */ virtual void handle_broken_connection(t_event_broken_connection *e) = 0; public: virtual ~t_transaction_layer() {}; // Client primitives void send_request(t_user *user_config, t_request *r, t_tuid tuid); void send_request(t_user *user_config, StunMessage *r, t_tuid tuid); // Server primitives void send_response(t_response *r, t_tuid tuid, t_tid tid); // Main loop void run(void); // Lock and unlocking methods for dedicated access to the // transaction layer. void lock(void) const; void unlock(void) const; }; #endif twinkle-1.10.1/src/transaction_mgr.cpp000066400000000000000000000453651277565361200177670ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include "log.h" #include "transaction_mgr.h" #include "sockets/url.h" #include "util.h" #include "audits/memman.h" extern t_event_queue *evq_trans_mgr; extern t_event_queue *evq_trans_layer; extern t_event_queue *evq_timekeeper; extern t_transaction_mgr *transaction_mgr; t_trans_client *t_transaction_mgr::find_trans_client(t_response *r) const { map::const_iterator i; for (i = map_trans_client.begin(); i != map_trans_client.end(); ++i) { if (i->second->match(r)) return i->second; } return NULL; } t_trans_client *t_transaction_mgr::find_trans_client(t_tid tid) const { map::const_iterator i; i = map_trans_client.find(tid); if (i == map_trans_client.end()) return NULL; return i->second; } t_trans_client *t_transaction_mgr::find_trans_client(const string &branch, const t_method &cseq_method) const { map::const_iterator i; for (i = map_trans_client.begin(); i != map_trans_client.end(); ++i) { if (i->second->match(branch, cseq_method)) return i->second; } return NULL; } t_trans_client *t_transaction_mgr::find_trans_client(const t_icmp_msg &icmp) const { map::const_iterator i; for (i = map_trans_client.begin(); i != map_trans_client.end(); ++i) { if (i->second->match(icmp)) return i->second; } return NULL; } t_trans_server *t_transaction_mgr::find_trans_server(t_request *r) const { map::const_iterator i; for (i = map_trans_server.begin(); i != map_trans_server.end(); i++) { if (i->second->match(r)) return i->second; } return NULL; } t_trans_server *t_transaction_mgr::find_trans_server(t_tid tid) const { map::const_iterator i; i = map_trans_server.find(tid); if (i == map_trans_server.end()) return NULL; return i->second; } t_stun_transaction *t_transaction_mgr::find_stun_trans(StunMessage *r) const { map::const_iterator i; for (i = map_stun_trans.begin(); i != map_stun_trans.end(); ++i) { if (i->second->match(r)) return i->second; } return NULL; } t_stun_transaction *t_transaction_mgr::find_stun_trans(t_tid tid) const { map::const_iterator i; i = map_stun_trans.find(tid); if (i == map_stun_trans.end()) return NULL; return i->second; } t_stun_transaction *t_transaction_mgr::find_stun_trans(const t_icmp_msg &icmp) const { map::const_iterator i; for (i = map_stun_trans.begin(); i != map_stun_trans.end(); ++i) { if (i->second->match(icmp)) return i->second; } return NULL; } t_trans_server *t_transaction_mgr::find_cancel_target(t_request *r) const { map::const_iterator i; for (i = map_trans_server.begin(); i != map_trans_server.end(); ++i) { if (i->second->match_cancel(r)) return i->second; } return NULL; } t_tc_invite *t_transaction_mgr::create_tc_invite(t_user *user_config, t_request *r, unsigned short tuid) { t_ip_port ip_port; r->get_destination(ip_port, *user_config); if (ip_port.ipaddr == 0 || ip_port.port == 0) return NULL; t_tc_invite *t = new t_tc_invite(r, ip_port, tuid); MEMMAN_NEW(t); map_trans_client[t->get_id()] = (t_trans_client *)t; return t; } t_tc_non_invite *t_transaction_mgr::create_tc_non_invite(t_user *user_config, t_request *r, unsigned short tuid) { t_ip_port ip_port; r->get_destination(ip_port, *user_config); if (ip_port.ipaddr == 0 || ip_port.port == 0) return NULL; t_tc_non_invite *t = new t_tc_non_invite(r, ip_port, tuid); MEMMAN_NEW(t); map_trans_client[t->get_id()] = (t_trans_client *)t; return t; } t_ts_invite *t_transaction_mgr::create_ts_invite(t_request *r) { t_ts_invite *t = new t_ts_invite(r, 0); MEMMAN_NEW(t); map_trans_server[t->get_id()] = (t_trans_server *)t; return t; } t_ts_non_invite *t_transaction_mgr::create_ts_non_invite(t_request *r) { t_ts_non_invite *t = new t_ts_non_invite(r, 0); MEMMAN_NEW(t); map_trans_server[t->get_id()] = (t_trans_server *)t; return t; } t_sip_stun_trans *t_transaction_mgr::create_sip_stun_trans(t_user *user_config, StunMessage *r, unsigned short tuid) { list destinations = user_config->get_stun_server().get_h_ip_srv("udp"); if (destinations.empty()) return NULL; t_sip_stun_trans *t = new t_sip_stun_trans(user_config, r, tuid, destinations); MEMMAN_NEW(t); map_stun_trans[t->get_id()] = (t_stun_transaction *)t; return t; } t_media_stun_trans *t_transaction_mgr::create_media_stun_trans(t_user *user_config, StunMessage *r, unsigned short tuid, unsigned short src_port) { list destinations = user_config->get_stun_server().get_h_ip_srv("udp"); if (destinations.empty()) return NULL; t_media_stun_trans *t = new t_media_stun_trans(user_config, r, tuid, destinations, src_port); MEMMAN_NEW(t); map_stun_trans[t->get_id()] = (t_stun_transaction *)t; return t; } void t_transaction_mgr::delete_trans_client(t_trans_client *tc) { map_trans_client.erase(tc->get_id()); MEMMAN_DELETE(tc); delete tc; } void t_transaction_mgr::delete_trans_server(t_trans_server *ts) { map_trans_server.erase(ts->get_id()); MEMMAN_DELETE(ts); delete ts; } void t_transaction_mgr::delete_stun_trans(t_stun_transaction *st) { map_stun_trans.erase(st->get_id()); MEMMAN_DELETE(st); delete st; } t_transaction_mgr::~t_transaction_mgr() { log_file->write_header("t_transaction_mgr::~t_transaction_mgr", LOG_NORMAL, LOG_INFO); log_file->write_raw("Clean up transaction manager.\n"); map::iterator i; for (i = map_trans_client.begin(); i != map_trans_client.end(); i++) { log_file->write_raw("\nDeleting client transaction: \n"); log_file->write_raw("Tid: "); log_file->write_raw(i->first); log_file->write_raw(", Method: "); log_file->write_raw(method2str(i->second->get_method())); log_file->write_raw(", State: "); log_file->write_raw(trans_state2str(i->second->get_state())); log_file->write_endl(); MEMMAN_DELETE(i->second); delete i->second; } map::iterator j; for (j = map_trans_server.begin(); j != map_trans_server.end(); j++) { log_file->write_raw("\nDeleting server transaction: \n"); log_file->write_raw("Tid: "); log_file->write_raw(j->first); log_file->write_raw(", Method: "); log_file->write_raw(method2str(j->second->get_method())); log_file->write_raw(", State: "); log_file->write_raw(trans_state2str(j->second->get_state())); log_file->write_endl(); MEMMAN_DELETE(j->second); delete j->second; } map::iterator k; for (k = map_stun_trans.begin(); k != map_stun_trans.end(); k++) { log_file->write_raw("\nDeleting STUN transaction: \n"); log_file->write_raw("Tid: "); log_file->write_raw(k->first); log_file->write_raw(", State: "); log_file->write_raw(trans_state2str(k->second->get_state())); log_file->write_endl(); MEMMAN_DELETE(k->second); delete k->second; } log_file->write_footer(); } void t_transaction_mgr::handle_event_network(t_event_network *e) { t_trans_server *ts; t_ts_invite *ts_invite; t_trans_client *tc; t_sip_message *msg = e->get_msg(); t_request *request; t_response *response; switch(msg->get_type()) { case MSG_REQUEST: // Request from network is for a server transaction request = (t_request *)msg; ts = find_trans_server(request); if (ts) { switch (request->method) { case ACK: // ACK for an INVITE transaction ts_invite = (t_ts_invite *)ts; ts_invite->acknowledge(request); break; default: // A request that matches an existing // transaction is a retransmission ts->process_retransmission(); break; } if (ts->get_state() == TS_TERMINATED) { delete_trans_server(ts); } return; } // Create a new transaction switch (request->method) { case INVITE: create_ts_invite(request); break; case ACK: // ACK should be passed to TU evq_trans_layer->push_user(request, 0, 0); break; default: create_ts_non_invite(request); break; } break; case MSG_RESPONSE: // Response from network is for a client transaction response = (t_response *)msg; tc = find_trans_client(response); if (!tc) { // Only a 2XX for an INVITE transaction can be // received while no transaction exists anymore. // RFC 3261 17.1.1.2 if (response->is_success() && response->hdr_cseq.method == INVITE) { // Report to TU evq_trans_layer->push_user(response, 0, 0); } else { log_file->write_report( "Response does not match any transaction. Discard.", "t_transaction_mgr::handle_event_network"); } break; } tc->process_response(response); if (tc->get_state() == TS_TERMINATED) { delete_trans_client(tc); } break; default: assert(false); break; } } void t_transaction_mgr::handle_event_user(t_event_user *e) { t_trans_server *ts; t_sip_message *msg = e->get_msg(); t_request *request; t_response *response; switch(msg->get_type()) { case MSG_REQUEST: // A user request creates a client transaction request = (t_request *)msg; switch (request->method) { case INVITE: t_tc_invite *t1; assert(e->get_user_config()); t1 = create_tc_invite(e->get_user_config(), request, e->get_tuid()); if (t1 == NULL) { // Report 404 to TU response = request->create_response( R_404_NOT_FOUND); log_file->write_header( "t_transaction_mgr::handle_event_user", LOG_NORMAL, LOG_INFO); log_file->write_raw("Cannot resolve destination for:\n"); log_file->write_raw(request->encode()); log_file->write_endl(); log_file->write_raw("Send internal:\n"); log_file->write_raw(response->encode()); log_file->write_footer(); evq_trans_layer->push_user(response, e->get_tuid(), 0); MEMMAN_DELETE(response); delete response; } break; default: t_tc_non_invite *t2; assert(e->get_user_config()); t2 = create_tc_non_invite(e->get_user_config(), request, e->get_tuid()); if (t2 == NULL) { // Report 404 to TU response = request->create_response( R_404_NOT_FOUND); log_file->write_header( "t_transaction_mgr::handle_event_user", LOG_NORMAL, LOG_INFO); log_file->write_raw("Cannot resolve destination for:\n"); log_file->write_raw(request->encode()); log_file->write_endl(); log_file->write_raw("Send internal:\n"); log_file->write_raw(response->encode()); log_file->write_footer(); evq_trans_layer->push_user(response, e->get_tuid(), 0); MEMMAN_DELETE(response); delete response; } break; } break; case MSG_RESPONSE: // A user repsonse is for a server transaction response = (t_response *)msg; ts = find_trans_server(e->get_tid()); if (!ts) { // This is an error. A response should match a // transaction. Ignore it. log_file->write_report( "Response from user does not match any transaction. Ignore.", "t_transaction_mgr::handle_event_user", LOG_NORMAL, LOG_WARNING); return; } ts->process_response(response); if (ts->get_state() == TS_TERMINATED) { delete_trans_server(ts); } break; default: assert(false); break; } } void t_transaction_mgr::handle_event_timeout(t_event_timeout *e) { t_timer *t = e->get_timer(); t_tmr_transaction *tmr_trans; t_tmr_stun_trans *tmr_stun_trans; t_tid tid; t_trans_client *tc; t_trans_server *ts; t_stun_transaction *st; switch (t->get_type()) { case TMR_TRANSACTION: tmr_trans = (t_tmr_transaction *)t; tid = tmr_trans->get_tid(); tc = find_trans_client(tid); if (tc) { tc->timeout(tmr_trans->get_sip_timer()); if (tc->get_state() == TS_TERMINATED) { delete_trans_client(tc); } return; } ts = find_trans_server(tid); if (ts) { ts->timeout(tmr_trans->get_sip_timer()); if (ts->get_state() == TS_TERMINATED) { delete_trans_server(ts); } return; } // The transaction is already gone. Discard timeout. break; case TMR_STUN_TRANSACTION: tmr_stun_trans = (t_tmr_stun_trans *)t; tid = tmr_stun_trans->get_tid(); st = find_stun_trans(tid); if (st) { st->timeout(tmr_stun_trans->get_stun_timer()); if (st->get_state() == TS_TERMINATED) { delete_stun_trans(st); } return; } // The transaction is already gone. Discard timeout. break; default: assert(false); break; } } void t_transaction_mgr::handle_event_abort(t_event_abort_trans *e) { t_tid tid; t_trans_client *tc; // Only a client transaction can be aborted. tid = e->get_tid(); tc = find_trans_client(tid); if (tc) { tc->abort(); if (tc->get_state() == TS_TERMINATED) { delete_trans_client(tc); } } } void t_transaction_mgr::handle_event_stun_request(t_event_stun_request *e) { StunMessage *msg = e->get_msg(); unsigned short tuid = e->get_tuid(); unsigned short tid = e->get_tid(); t_sip_stun_trans *sst; t_media_stun_trans *mst; StunMessage *resp; switch(e->get_stun_event_type()) { case TYPE_STUN_SIP: assert(e->get_user_config()); sst = create_sip_stun_trans(e->get_user_config(), msg, tuid); if (!sst) { // STUN server not found log_file->write_header( "t_transaction_mgr::handle_event_stun_request", LOG_NORMAL, LOG_INFO); log_file->write_raw("Cannot resolve:\n"); log_file->write_raw(e->get_user_config()->get_stun_server().encode()); log_file->write_endl(); log_file->write_raw("Send internal: 404 Not Found\n"); log_file->write_footer(); resp = stunBuildError(*msg, 404, "Not Found"); evq_trans_layer->push_stun_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; } break; case TYPE_STUN_MEDIA: assert(e->get_user_config()); mst = create_media_stun_trans(e->get_user_config(), msg, tuid, e->src_port); if (!mst) { // STUN server not found log_file->write_header( "t_transaction_mgr::handle_event_stun_request", LOG_NORMAL, LOG_INFO); log_file->write_raw("Cannot resolve:\n"); log_file->write_raw(e->get_user_config()->get_stun_server().encode()); log_file->write_endl(); log_file->write_raw("Send internal: 404 Not Found\n"); log_file->write_footer(); resp = stunBuildError(*msg, 404, "Not Found"); evq_trans_layer->push_stun_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; } break; default: assert(false); break; } } void t_transaction_mgr::handle_event_stun_response(t_event_stun_response *e) { StunMessage *response = e->get_msg(); t_stun_transaction *st = find_stun_trans(response); if (!st) { // This response does not match any transaction. // Ignore it. return; } st->process_response(response); if (st->get_state() == TS_TERMINATED) { delete_stun_trans(st); } } void t_transaction_mgr::handle_event_icmp(t_event_icmp *e) { // Only a client and STUN transactions can handle ICMP errors // If both a client and STUN transaction match then send the ICMP // error to both transactions. It cannot be determined which transaction // caused the error, but as both transactions have the same destination // it is likely that both will fail. t_trans_client *tc = find_trans_client(e->get_icmp()); if (tc) { tc->process_icmp(e->get_icmp()); if (tc->get_state() == TS_TERMINATED) { delete_trans_client(tc); } } t_stun_transaction *st = find_stun_trans(e->get_icmp()); if (st) { st->process_icmp(e->get_icmp()); if (st->get_state() == TS_TERMINATED) { delete_stun_trans(st); } } } void t_transaction_mgr::handle_event_failure(t_event_failure *e) { // Only a client transaction can handle failure events. t_trans_client *tc; if (e->is_tid_populated()) { tc = find_trans_client(e->get_tid()); } else { tc = find_trans_client(e->get_branch(), e->get_cseq_method()); } if (tc) { tc->process_failure(e->get_failure()); if (tc->get_state() == TS_TERMINATED) { delete_trans_client(tc); } } } t_object_id t_transaction_mgr::start_timer(long dur, t_sip_timer tmr, unsigned short tid) { t_tmr_transaction *t = new t_tmr_transaction(dur, tmr, tid); MEMMAN_NEW(t); evq_timekeeper->push_start_timer(t); t_object_id timer_id = t->get_object_id(); MEMMAN_DELETE(t); delete t; return timer_id; } t_object_id t_transaction_mgr::start_stun_timer(long dur, t_stun_timer tmr, unsigned short tid) { t_tmr_stun_trans *t = new t_tmr_stun_trans(dur, tmr, tid); MEMMAN_NEW(t); evq_timekeeper->push_start_timer(t); t_object_id timer_id = t->get_object_id(); MEMMAN_DELETE(t); delete t; return timer_id; } void t_transaction_mgr::stop_timer(t_object_id id) { evq_timekeeper->push_stop_timer(id); } void t_transaction_mgr::run(void) { t_event *event; t_event_network *ev_network; t_event_user *ev_user; t_event_timeout *ev_timeout; t_event_abort_trans *ev_abort; t_event_stun_request *ev_stun_request; t_event_stun_response *ev_stun_response; t_event_icmp *ev_icmp; t_event_failure *ev_failure; bool quit = false; while (!quit) { event = evq_trans_mgr->pop(); switch (event->get_type()) { case EV_NETWORK: ev_network = dynamic_cast(event); handle_event_network(ev_network); break; case EV_USER: ev_user = dynamic_cast(event); handle_event_user(ev_user); break; case EV_TIMEOUT: ev_timeout = dynamic_cast(event); handle_event_timeout(ev_timeout); break; case EV_ABORT_TRANS: ev_abort = dynamic_cast(event); handle_event_abort(ev_abort); break; case EV_STUN_REQUEST: ev_stun_request = dynamic_cast(event); handle_event_stun_request(ev_stun_request); break; case EV_STUN_RESPONSE: ev_stun_response = dynamic_cast(event); handle_event_stun_response(ev_stun_response); break; case EV_ICMP: ev_icmp = dynamic_cast(event); handle_event_icmp(ev_icmp); break; case EV_FAILURE: ev_failure = dynamic_cast(event); handle_event_failure(ev_failure); break; case EV_QUIT: quit = true; break; default: assert(false); break; } MEMMAN_DELETE(event); delete event; } } // Main function to be started in a separate thread. void *transaction_mgr_main(void *arg) { transaction_mgr->run(); return NULL; } twinkle-1.10.1/src/transaction_mgr.h000066400000000000000000000071411277565361200174220ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _TRANSACTION_MGR_H #define _TRANSACTION_MGR_H #include #include "events.h" #include "transaction.h" #include "user.h" #include "parser/request.h" #include "parser/response.h" #include "sockets/socket.h" #include "stun/stun_transaction.h" using namespace std; class t_transaction_mgr { private: // Mapping from transaction id to transaction map map_trans_client; map map_trans_server; map map_stun_trans; // Find existing transactions. Return NULL if not found t_trans_client *find_trans_client(t_response *r) const; t_trans_client *find_trans_client(t_tid tid) const; t_trans_client *find_trans_client(const string &branch, const t_method &cseq_method) const; t_trans_client *find_trans_client(const t_icmp_msg &icmp) const; t_trans_server *find_trans_server(t_request *r) const; t_trans_server *find_trans_server(t_tid tid) const; t_stun_transaction *find_stun_trans(StunMessage *r) const; t_stun_transaction *find_stun_trans(t_tid tid) const; t_stun_transaction *find_stun_trans(const t_icmp_msg &icmp) const; // Create new transactions. // Return NULL if creation failed. t_tc_invite *create_tc_invite(t_user *user_config, t_request *r, unsigned short tuid); t_tc_non_invite *create_tc_non_invite(t_user *user_config, t_request *r, unsigned short tuid); t_ts_invite *create_ts_invite(t_request *r); t_ts_non_invite *create_ts_non_invite(t_request *r); t_sip_stun_trans *create_sip_stun_trans(t_user *user_config, StunMessage *r, unsigned short tuid); t_media_stun_trans *create_media_stun_trans(t_user *user_config, StunMessage *r, unsigned short tuid, unsigned short src_port); // Delete transactions void delete_trans_client(t_trans_client *tc); void delete_trans_server(t_trans_server *ts); void delete_stun_trans(t_stun_transaction *st); // Handle events void handle_event_network(t_event_network *e); void handle_event_user(t_event_user *e); void handle_event_timeout(t_event_timeout *e); void handle_event_abort(t_event_abort_trans *e); void handle_event_stun_request(t_event_stun_request *e); void handle_event_stun_response(t_event_stun_response *e); void handle_event_icmp(t_event_icmp *e); void handle_event_failure(t_event_failure *e); public: ~t_transaction_mgr(); // Find the target transaction for a CANCEL. // Return NULL if not found. t_trans_server *find_cancel_target(t_request *r) const; // Start transaction timer. Return timer id (needed for stopping) t_object_id start_timer(long dur, t_sip_timer tmr, unsigned short tid); t_object_id start_stun_timer(long dur, t_stun_timer tmr, unsigned short tid); // Stop timer. Pass id that is returned by start_timer void stop_timer(t_object_id id); // Main loop of the transaction manager (infinite) void run (void); }; // Thread that runs the transaction manager void *transaction_mgr_main(void *arg); #endif twinkle-1.10.1/src/translator.h000066400000000000000000000031401277565361200164140ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _TRANSLATOR_H #define _TRANSLATOR_H #include #define TRANSLATE(s) (translator ? translator->translate(s) : s) #define TRANSLATE2(c, s) (translator ? translator->translate2(c, s) : s) using namespace std; // This class provides an interface for languague translations. // The default implementation does not perform any translation. // The class may be subclassed to provide translation services. class t_translator { public: virtual ~t_translator() {}; // The default implementation simply returns the passed // string. A subclass should reimplement this method to // provide translation. virtual string translate(const string &s) { return s; }; // The name of the context parameter is in comments to avoid // unused argument warnings. virtual string translate2(const string &/*context*/, const string &s) { return s; }; }; extern t_translator *translator; #endif twinkle-1.10.1/src/user.cpp000066400000000000000000002444141277565361200155470ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include #include #include #include #include #include "diamondcard.h" #include "log.h" #include "phone.h" #include "twinkle_config.h" #include "user.h" #include "userintf.h" #include "util.h" #include "protocol.h" #include "sys_settings.h" #include "audits/memman.h" #include "sdp/sdp.h" #include "parser/parse_ctrl.h" #include "parser/request.h" extern t_phone *phone; // Field names in the config file // USER fields #define FLD_NAME "user_name" #define FLD_DOMAIN "user_domain" #define FLD_DISPLAY "user_display" #define FLD_ORGANIZATION "user_organization" #define FLD_AUTH_REALM "auth_realm" #define FLD_AUTH_NAME "auth_name" #define FLD_AUTH_PASS "auth_pass" #define FLD_AUTH_AKA_OP "auth_aka_op" #define FLD_AUTH_AKA_AMF "auth_aka_amf" // SIP SERVER fields #define FLD_OUTBOUND_PROXY "outbound_proxy" #define FLD_ALL_REQUESTS_TO_PROXY "all_requests_to_proxy" #define FLD_NON_RESOLVABLE_TO_PROXY "non_resolvable_to_proxy" #define FLD_REGISTRAR "registrar" #define FLD_REGISTRATION_TIME "registration_time" #define FLD_REGISTER_AT_STARTUP "register_at_startup" #define FLD_REG_ADD_QVALUE "reg_add_qvalue" #define FLD_REG_QVALUE "reg_qvalue" // AUDIO fields #define FLD_CODECS "codecs" #define FLD_PTIME "ptime" #define FLD_OUT_FAR_END_CODEC_PREF "out_far_end_codec_pref" #define FLD_IN_FAR_END_CODEC_PREF "in_far_end_codec_pref" #define FLD_SPEEX_NB_PAYLOAD_TYPE "speex_nb_payload_type" #define FLD_SPEEX_WB_PAYLOAD_TYPE "speex_wb_payload_type" #define FLD_SPEEX_UWB_PAYLOAD_TYPE "speex_uwb_payload_type" #define FLD_SPEEX_BIT_RATE_TYPE "speex_bit_rate_type" #define FLD_SPEEX_ABR_NB "speex_abr_nb" #define FLD_SPEEX_ABR_WB "speex_abr_wb" #define FLD_SPEEX_DTX "speex_dtx" #define FLD_SPEEX_PENH "speex_penh" #define FLD_SPEEX_QUALITY "speex_quality" #define FLD_SPEEX_COMPLEXITY "speex_complexity" #define FLD_SPEEX_DSP_VAD "speex_dsp_vad" #define FLD_SPEEX_DSP_AGC "speex_dsp_agc" #define FLD_SPEEX_DSP_AGC_LEVEL "speex_dsp_agc_level" #define FLD_SPEEX_DSP_AEC "speex_dsp_aec" #define FLD_SPEEX_DSP_NRD "speex_dsp_nrd" #define FLD_ILBC_PAYLOAD_TYPE "ilbc_payload_type" #define FLD_ILBC_MODE "ilbc_mode" #define FLD_G726_16_PAYLOAD_TYPE "g726_16_payload_type" #define FLD_G726_24_PAYLOAD_TYPE "g726_24_payload_type" #define FLD_G726_32_PAYLOAD_TYPE "g726_32_payload_type" #define FLD_G726_40_PAYLOAD_TYPE "g726_40_payload_type" #define FLD_G726_PACKING "g726_packing" #define FLD_DTMF_TRANSPORT "dtmf_transport" #define FLD_DTMF_PAYLOAD_TYPE "dtmf_payload_type" #define FLD_DTMF_DURATION "dtmf_duration" #define FLD_DTMF_PAUSE "dtmf_pause" #define FLD_DTMF_VOLUME "dtmf_volume" // SIP PROTOCOL fields #define FLD_HOLD_VARIANT "hold_variant" #define FLD_CHECK_MAX_FORWARDS "check_max_forwards" #define FLD_ALLOW_MISSING_CONTACT_REG "allow_missing_contact_reg" #define FLD_REGISTRATION_TIME_IN_CONTACT "registration_time_in_contact" #define FLD_COMPACT_HEADERS "compact_headers" #define FLD_ENCODE_MULTI_VALUES_AS_LIST "encode_multi_values_as_list" #define FLD_USE_DOMAIN_IN_CONTACT "use_domain_in_contact" #define FLD_ALLOW_SDP_CHANGE "allow_sdp_change" #define FLD_ALLOW_REDIRECTION "allow_redirection" #define FLD_ASK_USER_TO_REDIRECT "ask_user_to_redirect" #define FLD_MAX_REDIRECTIONS "max_redirections" #define FLD_EXT_100REL "ext_100rel" #define FLD_EXT_REPLACES "ext_replaces" #define FLD_REFEREE_HOLD "referee_hold" #define FLD_REFERRER_HOLD "referrer_hold" #define FLD_ALLOW_REFER "allow_refer" #define FLD_ASK_USER_TO_REFER "ask_user_to_refer" #define FLD_AUTO_REFRESH_REFER_SUB "auto_refresh_refer_sub" #define FLD_ATTENDED_REFER_TO_AOR "attended_refer_to_aor" #define FLD_ALLOW_XFER_CONSULT_INPROG "allow_xfer_consult_inprog" #define FLD_SEND_P_PREFERRED_ID "send_p_preferred_id" // Transport/NAT fields #define FLD_SIP_TRANSPORT "sip_transport" #define FLD_SIP_TRANSPORT_UDP_THRESHOLD "sip_transport_udp_threshold" #define FLD_NAT_PUBLIC_IP "nat_public_ip" #define FLD_STUN_SERVER "stun_server" #define FLD_PERSISTENT_TCP "persistent_tcp" #define FLD_ENABLE_NAT_KEEPALIVE "enable_nat_keepalive" // TIMER fields #define FLD_TIMER_NOANSWER "timer_noanswer" #define FLD_TIMER_NAT_KEEPALIVE "timer_nat_keepalive" #define FLD_TIMER_TCP_PING "timer_tcp_ping" // ADDRESS FORMAT fields #define FLD_DISPLAY_USERONLY_PHONE "display_useronly_phone" #define FLD_NUMERICAL_USER_IS_PHONE "numerical_user_is_phone" #define FLD_REMOVE_SPECIAL_PHONE_SYM "remove_special_phone_symbols" #define FLD_SPECIAL_PHONE_SYMBOLS "special_phone_symbols" #define FLD_USE_TEL_URI_FOR_PHONE "use_tel_uri_for_phone" // Ring tone settings #define FLD_USER_RINGTONE_FILE "ringtone_file" #define FLD_USER_RINGBACK_FILE "ringback_file" // Incoming call script #define FLD_SCRIPT_INCOMING_CALL "script_incoming_call" #define FLD_SCRIPT_IN_CALL_ANSWERED "script_in_call_answered" #define FLD_SCRIPT_IN_CALL_FAILED "script_in_call_failed" #define FLD_SCRIPT_OUTGOING_CALL "script_outgoing_call" #define FLD_SCRIPT_OUT_CALL_ANSWERED "script_out_call_answered" #define FLD_SCRIPT_OUT_CALL_FAILED "script_out_call_failed" #define FLD_SCRIPT_LOCAL_RELEASE "script_local_release" #define FLD_SCRIPT_REMOTE_RELEASE "script_remote_release" // Number conversion #define FLD_NUMBER_CONVERSION "number_conversion" // Security #define FLD_ZRTP_ENABLED "zrtp_enabled" #define FLD_ZRTP_GOCLEAR_WARNING "zrtp_goclear_warning" #define FLD_ZRTP_SDP "zrtp_sdp" #define FLD_ZRTP_SEND_IF_SUPPORTED "zrtp_send_if_supported" // MWI #define FLD_MWI_SOLLICITED "mwi_sollicited" #define FLD_MWI_USER "mwi_user" #define FLD_MWI_SERVER "mwi_server" #define FLD_MWI_VIA_PROXY "mwi_via_proxy" #define FLD_MWI_SUBSCRIPTION_TIME "mwi_subscription_time" #define FLD_MWI_VM_ADDRESS "mwi_vm_address" // INSTANT MESSAGE #define FLD_IM_MAX_SESSIONS "im_max_sessions" #define FLD_IM_SEND_ISCOMPOSING "im_send_iscomposing" // PRESENCE #define FLD_PRES_SUBSCRIPTION_TIME "pres_subscription_time" #define FLD_PRES_PUBLICATION_TIME "pres_publication_time" #define FLD_PRES_PUBLISH_STARTUP "pres_publish_startup" ///////////////////////// // class t_user ///////////////////////// //////////////////// // Private //////////////////// t_ext_support t_user::str2ext_support(const string &s) const { if (s == "disabled") return EXT_DISABLED; if (s == "supported") return EXT_SUPPORTED; if (s == "preferred") return EXT_PREFERRED; if (s == "required") return EXT_REQUIRED; return EXT_INVALID; } string t_user::ext_support2str(t_ext_support e) const { switch(e) { case EXT_INVALID: return "invalid"; case EXT_DISABLED: return "disabled"; case EXT_SUPPORTED: return "supported"; case EXT_PREFERRED: return "preferred"; case EXT_REQUIRED: return "required"; default: assert(false); } return ""; } t_bit_rate_type t_user::str2bit_rate_type(const string &s) const { if (s == "cbr") return BIT_RATE_CBR; if (s == "vbr") return BIT_RATE_VBR; if (s == "abr") return BIT_RATE_ABR; return BIT_RATE_INVALID; } string t_user::bit_rate_type2str(t_bit_rate_type b) const { switch (b) { case BIT_RATE_INVALID: return "invalid"; case BIT_RATE_CBR: return "cbr"; case BIT_RATE_VBR: return "vbr"; case BIT_RATE_ABR: return "abr"; default: assert(false); } return ""; } t_dtmf_transport t_user::str2dtmf_transport(const string &s) const { if (s == "inband") return DTMF_INBAND; if (s == "rfc2833") return DTMF_RFC2833; if (s == "auto") return DTMF_AUTO; if (s == "info") return DTMF_INFO; return DTMF_AUTO; } string t_user::dtmf_transport2str(t_dtmf_transport d) const { switch (d) { case DTMF_INBAND: return "inband"; case DTMF_RFC2833: return "rfc2833"; case DTMF_AUTO: return "auto"; case DTMF_INFO: return "info"; default: assert(false); } return ""; } t_g726_packing t_user::str2g726_packing(const string &s) const { if (s == "rfc3551") return G726_PACK_RFC3551; if (s == "aal2") return G726_PACK_AAL2; return G726_PACK_AAL2; } string t_user::g726_packing2str(t_g726_packing packing) const { switch (packing) { case G726_PACK_RFC3551: return "rfc3551"; case G726_PACK_AAL2: return "aal2"; default: assert(false); } return ""; } t_sip_transport t_user::str2sip_transport(const string &s) const { if (s == "udp") return SIP_TRANS_UDP; if (s == "tcp") return SIP_TRANS_TCP; if (s == "auto") return SIP_TRANS_AUTO; return SIP_TRANS_AUTO; } string t_user::sip_transport2str(t_sip_transport transport) const { switch (transport) { case SIP_TRANS_UDP: return "udp"; case SIP_TRANS_TCP: return "tcp"; case SIP_TRANS_AUTO: return "auto"; default: assert(false); } return ""; } string t_user::expand_filename(const string &filename) { string f; if (filename[0] == '/') { f = filename; } else { f = string(DIR_HOME); f += "/"; f += USER_DIR; f += "/"; f += filename; } return f; } bool t_user::parse_num_conversion(const string &value, t_number_conversion &c) { vector l = split_escaped(value, ','); if (l.size() != 2) { // Invalid conversion rule return false; } try { c.re.assign(l[0]); c.fmt = l[1]; } catch (std::regex_error) { // Invalid regular expression log_file->write_header("t_user::parse_num_conversion", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Bad number conversion:\n"); log_file->write_raw(l.front()); log_file->write_raw(" --> "); log_file->write_raw(l.back()); log_file->write_endl(); log_file->write_footer(); return false; } return true; } bool t_user::set_server_value(t_url &server, const string &scheme, const string &value) { if (value.empty()) { server.set_url(""); return false; } string s = scheme + ":" + value; server.set_url(s); if (!server.is_valid() || server.get_user() != "") { string err_msg = "Invalid server value: "; err_msg += value; log_file->write_report(err_msg, "t_user::set_server_value", LOG_NORMAL, LOG_WARNING); server.set_url(""); return false; } return true; } //////////////////// // Public //////////////////// t_user::t_user() { // Set defaults memset(auth_aka_op, 0, AKA_OPLEN); memset(auth_aka_amf, 0, AKA_AMFLEN); use_outbound_proxy = false; all_requests_to_proxy = false; non_resolvable_to_proxy = false; use_registrar = false; registration_time = 3600; #ifdef HAVE_SPEEX codecs.push_back(CODEC_SPEEX_WB); codecs.push_back(CODEC_SPEEX_NB); #endif #ifdef HAVE_ILBC codecs.push_back(CODEC_ILBC); #endif codecs.push_back(CODEC_G711_ALAW); codecs.push_back(CODEC_G711_ULAW); codecs.push_back(CODEC_GSM); #ifdef HAVE_BCG729 codecs.push_back(CODEC_G729A); #endif ptime = 20; out_obey_far_end_codec_pref = true; in_obey_far_end_codec_pref = true; hold_variant = HOLD_RFC3264; use_nat_public_ip = false; use_stun = false; persistent_tcp = true; enable_nat_keepalive = false; register_at_startup = true; reg_add_qvalue = false; reg_qvalue = 1.0; check_max_forwards = false; allow_missing_contact_reg = true; compact_headers = false; encode_multi_values_as_list = true; registration_time_in_contact = true; use_domain_in_contact = false; allow_sdp_change = false; allow_redirection = true; ask_user_to_redirect = true; max_redirections = 5; timer_noanswer = 30; timer_nat_keepalive = DUR_NAT_KEEPALIVE; timer_tcp_ping = DUR_TCP_PING; ext_100rel = EXT_SUPPORTED; ext_replaces = true; speex_nb_payload_type = 97; speex_wb_payload_type = 98; speex_uwb_payload_type = 99; speex_bit_rate_type = BIT_RATE_CBR; speex_abr_nb = 0; speex_abr_wb = 0; speex_dtx = false; speex_penh = true; speex_quality = 6; speex_complexity = 3; speex_dsp_vad = true; speex_dsp_agc = true; speex_dsp_aec = false; speex_dsp_nrd = true; speex_dsp_agc_level = 20; ilbc_payload_type = 96; ilbc_mode = 30; g726_16_payload_type = 102; g726_24_payload_type = 103; g726_32_payload_type = 104; g726_40_payload_type = 105; g726_packing = G726_PACK_RFC3551; dtmf_transport = DTMF_AUTO; dtmf_duration = 100; dtmf_pause = 40; dtmf_payload_type = 101; dtmf_volume = 10; display_useronly_phone = true; numerical_user_is_phone = false; remove_special_phone_symbols = true; special_phone_symbols = SPECIAL_PHONE_SYMBOLS; use_tel_uri_for_phone = false; referee_hold = false; referrer_hold = true; allow_refer = true; ask_user_to_refer = true; auto_refresh_refer_sub = false; attended_refer_to_aor = false; allow_transfer_consultation_inprog = false; send_p_preferred_id = false; sip_transport = SIP_TRANS_AUTO; sip_transport_udp_threshold = 1300; // RFC 3261 18.1.1 ringtone_file.clear(); ringback_file.clear(); script_incoming_call.clear(); script_in_call_answered.clear(); script_in_call_failed.clear(); script_outgoing_call.clear(); script_out_call_answered.clear(); script_out_call_failed.clear(); script_local_release.clear(); script_remote_release.clear(); number_conversions.clear(); zrtp_enabled = false; zrtp_goclear_warning = true; zrtp_sdp = true; zrtp_send_if_supported = false; mwi_sollicited = false; mwi_user.clear(); mwi_via_proxy = false; mwi_subscription_time = 3600; mwi_vm_address.clear(); im_max_sessions = 10; im_send_iscomposing = true; pres_subscription_time = 3600; pres_publication_time = 3600; pres_publish_startup = true; } t_user::t_user(const t_user &u) { u.mtx_user.lock(); config_filename = u.config_filename; name = u.name; domain = u.domain; display = u.display; organization = u.organization; auth_realm = u.auth_realm; auth_name = u.auth_name; auth_pass = u.auth_pass; memcpy(auth_aka_op, u.auth_aka_op, AKA_OPLEN); memcpy(auth_aka_amf, u.auth_aka_amf, AKA_AMFLEN); use_outbound_proxy = u.use_outbound_proxy; outbound_proxy = u.outbound_proxy; all_requests_to_proxy = u.all_requests_to_proxy; non_resolvable_to_proxy = u.non_resolvable_to_proxy; use_registrar = u.use_registrar; reg_add_qvalue = u.reg_add_qvalue; reg_qvalue = u.reg_qvalue; registrar = u.registrar; registration_time = u.registration_time; register_at_startup = u.register_at_startup; codecs = u.codecs; ptime = u.ptime; out_obey_far_end_codec_pref = u.out_obey_far_end_codec_pref; in_obey_far_end_codec_pref = u.in_obey_far_end_codec_pref; speex_nb_payload_type = u.speex_nb_payload_type; speex_wb_payload_type = u.speex_wb_payload_type; speex_uwb_payload_type = u.speex_uwb_payload_type; speex_bit_rate_type = u.speex_bit_rate_type; speex_abr_nb = u.speex_abr_nb; speex_abr_wb = u.speex_abr_wb; speex_dtx = u.speex_dtx; speex_penh = u.speex_penh; speex_quality = u.speex_quality; speex_complexity = u.speex_complexity; speex_dsp_vad = u.speex_dsp_vad; speex_dsp_agc = u.speex_dsp_agc; speex_dsp_agc_level = u.speex_dsp_agc_level; speex_dsp_aec = u.speex_dsp_aec; speex_dsp_nrd = u.speex_dsp_nrd; ilbc_payload_type = u.ilbc_payload_type; ilbc_mode = u.ilbc_mode; g726_16_payload_type = u.g726_16_payload_type; g726_24_payload_type = u.g726_24_payload_type; g726_32_payload_type = u.g726_32_payload_type; g726_40_payload_type = u.g726_40_payload_type; g726_packing = u.g726_packing; dtmf_transport = u.dtmf_transport; dtmf_payload_type = u.dtmf_payload_type; dtmf_duration = u.dtmf_duration; dtmf_pause = u.dtmf_pause; dtmf_volume = u.dtmf_volume; hold_variant = u.hold_variant; check_max_forwards = u.check_max_forwards; allow_missing_contact_reg = u.allow_missing_contact_reg; registration_time_in_contact = u.registration_time_in_contact; compact_headers = u.compact_headers; encode_multi_values_as_list = u.encode_multi_values_as_list; use_domain_in_contact = u.use_domain_in_contact; allow_sdp_change = u.allow_sdp_change; allow_redirection = u.allow_redirection; ask_user_to_redirect = u.ask_user_to_redirect; max_redirections = u.max_redirections; ext_100rel = u.ext_100rel; ext_replaces = u.ext_replaces; referee_hold = u.referee_hold; referrer_hold = u.referrer_hold; allow_refer = u.allow_refer; ask_user_to_refer = u.ask_user_to_refer; auto_refresh_refer_sub = u.auto_refresh_refer_sub; attended_refer_to_aor = u.attended_refer_to_aor; allow_transfer_consultation_inprog = u.allow_transfer_consultation_inprog; send_p_preferred_id = u.send_p_preferred_id; sip_transport = u.sip_transport; sip_transport_udp_threshold = u.sip_transport_udp_threshold; use_nat_public_ip = u.use_nat_public_ip; nat_public_ip = u.nat_public_ip; use_stun = u.use_stun; stun_server = u.stun_server; persistent_tcp = u.persistent_tcp; enable_nat_keepalive = u.enable_nat_keepalive; timer_noanswer = u.timer_noanswer; timer_nat_keepalive = u.timer_nat_keepalive; timer_tcp_ping = u.timer_tcp_ping; display_useronly_phone = u.display_useronly_phone; numerical_user_is_phone = u.numerical_user_is_phone; remove_special_phone_symbols = u.remove_special_phone_symbols; special_phone_symbols = u.special_phone_symbols; use_tel_uri_for_phone = u.use_tel_uri_for_phone; ringtone_file = u.ringtone_file; ringback_file = u.ringback_file; script_incoming_call = u.script_incoming_call; script_in_call_answered = u.script_in_call_answered; script_in_call_failed = u.script_in_call_failed; script_outgoing_call = u.script_outgoing_call; script_out_call_answered = u.script_out_call_answered; script_out_call_failed = u.script_out_call_failed; script_local_release = u.script_local_release; script_remote_release = u.script_remote_release; number_conversions = u.number_conversions; zrtp_enabled = u.zrtp_enabled; zrtp_goclear_warning = u.zrtp_goclear_warning; zrtp_sdp = u.zrtp_sdp; zrtp_send_if_supported = u.zrtp_send_if_supported; mwi_sollicited = u.mwi_sollicited; mwi_user = u.mwi_user; mwi_server = u.mwi_server; mwi_via_proxy = u.mwi_via_proxy; mwi_subscription_time = u.mwi_subscription_time; mwi_vm_address = u.mwi_vm_address; im_max_sessions = u.im_max_sessions; im_send_iscomposing = u.im_send_iscomposing; pres_subscription_time = u.pres_subscription_time; pres_publication_time = u.pres_publication_time; pres_publish_startup = u.pres_publish_startup; u.mtx_user.unlock(); } t_user *t_user::copy(void) const { t_user *u = new t_user(*this); MEMMAN_NEW(u); return u; } string t_user::get_name(void) const { string result; mtx_user.lock(); result = name; mtx_user.unlock(); return result; } string t_user::get_domain(void) const { string result; mtx_user.lock(); result = domain; mtx_user.unlock(); return result; } string t_user::get_display(bool anonymous) const { if (anonymous) return ANONYMOUS_DISPLAY; string result; mtx_user.lock(); result = display; mtx_user.unlock(); return result; } string t_user::get_organization(void) const { string result; mtx_user.lock(); result = organization; mtx_user.unlock(); return result; } string t_user::get_auth_realm(void) const { string result; mtx_user.lock(); result = auth_realm; mtx_user.unlock(); return result; } string t_user::get_auth_name(void) const { string result; mtx_user.lock(); result = auth_name; mtx_user.unlock(); return result; } string t_user::get_auth_pass(void) const { string result; mtx_user.lock(); result = auth_pass; mtx_user.unlock(); return result; } void t_user::get_auth_aka_op(uint8 *aka_op) const { t_mutex_guard guard(mtx_user); memcpy(aka_op, auth_aka_op, AKA_OPLEN); } void t_user::get_auth_aka_amf(uint8 *aka_amf) const { t_mutex_guard guard(mtx_user); memcpy(aka_amf, auth_aka_amf, AKA_AMFLEN); } bool t_user::get_use_outbound_proxy(void) const { bool result; mtx_user.lock(); result = use_outbound_proxy; mtx_user.unlock(); return result; } t_url t_user::get_outbound_proxy(void) const { t_url result; mtx_user.lock(); result = outbound_proxy; mtx_user.unlock(); return result; } bool t_user::get_all_requests_to_proxy(void) const { bool result; mtx_user.lock(); result = all_requests_to_proxy; mtx_user.unlock(); return result; } bool t_user::get_non_resolvable_to_proxy(void) const { bool result; mtx_user.lock(); result = non_resolvable_to_proxy; mtx_user.unlock(); return result; } bool t_user::get_use_registrar(void) const { bool result; mtx_user.lock(); result = use_registrar; mtx_user.unlock(); return result; } t_url t_user::get_registrar(void) const { t_url result; mtx_user.lock(); result = registrar; mtx_user.unlock(); return result; } unsigned long t_user::get_registration_time(void) const { unsigned long result; mtx_user.lock(); result = registration_time; mtx_user.unlock(); return result; } bool t_user::get_register_at_startup(void) const { bool result; mtx_user.lock(); result = register_at_startup; mtx_user.unlock(); return result; } bool t_user::get_reg_add_qvalue(void) const { bool result; mtx_user.lock(); result = reg_add_qvalue; mtx_user.unlock(); return result; } float t_user::get_reg_qvalue(void) const { float result; mtx_user.lock(); result = reg_qvalue; mtx_user.unlock(); return result; } list t_user::get_codecs(void) const { list result; mtx_user.lock(); result = codecs; mtx_user.unlock(); return result; } unsigned short t_user::get_ptime(void) const { unsigned short result; mtx_user.lock(); result = ptime; mtx_user.unlock(); return result; } bool t_user::get_out_obey_far_end_codec_pref(void) const { bool result; mtx_user.lock(); result = out_obey_far_end_codec_pref; mtx_user.unlock(); return result; } bool t_user::get_in_obey_far_end_codec_pref(void) const { bool result; mtx_user.lock(); result = in_obey_far_end_codec_pref; mtx_user.unlock(); return result; } unsigned short t_user::get_speex_nb_payload_type(void) const { unsigned short result; mtx_user.lock(); result = speex_nb_payload_type; mtx_user.unlock(); return result; } unsigned short t_user::get_speex_wb_payload_type(void) const { unsigned short result; mtx_user.lock(); result = speex_wb_payload_type; mtx_user.unlock(); return result; } unsigned short t_user::get_speex_uwb_payload_type(void) const { unsigned short result; mtx_user.lock(); result = speex_uwb_payload_type; mtx_user.unlock(); return result; } t_bit_rate_type t_user::get_speex_bit_rate_type(void) const { t_bit_rate_type result; mtx_user.lock(); result = speex_bit_rate_type; mtx_user.unlock(); return result; } int t_user::get_speex_abr_nb(void) const { int result; mtx_user.lock(); result = speex_abr_nb; mtx_user.unlock(); return result; } int t_user::get_speex_abr_wb(void) const { int result; mtx_user.lock(); result = speex_abr_wb; mtx_user.unlock(); return result; } bool t_user::get_speex_dtx(void) const { bool result; mtx_user.lock(); result = speex_dtx; mtx_user.unlock(); return result; } bool t_user::get_speex_penh(void) const { bool result; mtx_user.lock(); result = speex_penh; mtx_user.unlock(); return result; } unsigned short t_user::get_speex_quality(void) const { unsigned short result; mtx_user.lock(); result = speex_quality; mtx_user.unlock(); return result; } unsigned short t_user::get_speex_complexity(void) const { unsigned short result; mtx_user.lock(); result = speex_complexity; mtx_user.unlock(); return result; } bool t_user::get_speex_dsp_vad(void) const { bool result; mtx_user.lock(); result = speex_dsp_vad; mtx_user.unlock(); return result; } bool t_user::get_speex_dsp_agc(void) const { bool result; mtx_user.lock(); result = speex_dsp_agc; mtx_user.unlock(); return result; } unsigned short t_user::get_speex_dsp_agc_level(void) const { unsigned short result; mtx_user.lock(); result = speex_dsp_agc_level; mtx_user.unlock(); return result; } bool t_user::get_speex_dsp_aec(void) const { bool result; mtx_user.lock(); result = speex_dsp_aec; mtx_user.unlock(); return result; } bool t_user::get_speex_dsp_nrd(void) const { bool result; mtx_user.lock(); result = speex_dsp_nrd; mtx_user.unlock(); return result; } unsigned short t_user::get_ilbc_payload_type(void) const { unsigned short result; mtx_user.lock(); result = ilbc_payload_type; mtx_user.unlock(); return result; } unsigned short t_user::get_ilbc_mode(void) const { unsigned short result; mtx_user.lock(); result = ilbc_mode; mtx_user.unlock(); return result; } unsigned short t_user::get_g726_16_payload_type(void) const { unsigned short result; mtx_user.lock(); result = g726_16_payload_type; mtx_user.unlock(); return result; } unsigned short t_user::get_g726_24_payload_type(void) const { unsigned short result; mtx_user.lock(); result = g726_24_payload_type; mtx_user.unlock(); return result; } unsigned short t_user::get_g726_32_payload_type(void) const { unsigned short result; mtx_user.lock(); result = g726_32_payload_type; mtx_user.unlock(); return result; } unsigned short t_user::get_g726_40_payload_type(void) const { unsigned short result; mtx_user.lock(); result = g726_40_payload_type; mtx_user.unlock(); return result; } t_g726_packing t_user::get_g726_packing(void) const { t_g726_packing result; mtx_user.lock(); result = g726_packing; mtx_user.unlock(); return result; } t_dtmf_transport t_user::get_dtmf_transport(void) const { t_dtmf_transport result; mtx_user.lock(); result = dtmf_transport; mtx_user.unlock(); return result; } unsigned short t_user::get_dtmf_payload_type(void) const { unsigned short result; mtx_user.lock(); result = dtmf_payload_type; mtx_user.unlock(); return result; } unsigned short t_user::get_dtmf_duration(void) const { unsigned short result; mtx_user.lock(); result = dtmf_duration; mtx_user.unlock(); return result; } unsigned short t_user::get_dtmf_pause(void) const { unsigned short result; mtx_user.lock(); result = dtmf_pause; mtx_user.unlock(); return result; } unsigned short t_user::get_dtmf_volume(void) const { unsigned short result; mtx_user.lock(); result = dtmf_volume; mtx_user.unlock(); return result; } t_hold_variant t_user::get_hold_variant(void) const { t_hold_variant result; mtx_user.lock(); result = hold_variant; mtx_user.unlock(); return result; } bool t_user::get_check_max_forwards(void) const { bool result; mtx_user.lock(); result = check_max_forwards; mtx_user.unlock(); return result; } bool t_user::get_allow_missing_contact_reg(void) const { bool result; mtx_user.lock(); result = allow_missing_contact_reg; mtx_user.unlock(); return result; } bool t_user::get_registration_time_in_contact(void) const { bool result; mtx_user.lock(); result = registration_time_in_contact; mtx_user.unlock(); return result; } bool t_user::get_compact_headers(void) const { bool result; mtx_user.lock(); result = compact_headers; mtx_user.unlock(); return result; } bool t_user::get_encode_multi_values_as_list(void) const { bool result; mtx_user.lock(); result = encode_multi_values_as_list; mtx_user.unlock(); return result; } bool t_user::get_use_domain_in_contact(void) const { bool result; mtx_user.lock(); result = use_domain_in_contact; mtx_user.unlock(); return result; } bool t_user::get_allow_sdp_change(void) const { bool result; mtx_user.lock(); result = allow_sdp_change; mtx_user.unlock(); return result; } bool t_user::get_allow_redirection(void) const { bool result; mtx_user.lock(); result = allow_redirection; mtx_user.unlock(); return result; } bool t_user::get_ask_user_to_redirect(void) const { bool result; mtx_user.lock(); result = ask_user_to_redirect; mtx_user.unlock(); return result; } unsigned short t_user::get_max_redirections(void) const { unsigned short result; mtx_user.lock(); result = max_redirections; mtx_user.unlock(); return result; } t_ext_support t_user::get_ext_100rel(void) const { t_ext_support result; mtx_user.lock(); result = ext_100rel; mtx_user.unlock(); return result; } bool t_user::get_ext_replaces(void) const { bool result; mtx_user.lock(); result = ext_replaces; mtx_user.unlock(); return result; } bool t_user::get_referee_hold(void) const { t_mutex_guard guard(mtx_user); return referee_hold; } bool t_user::get_referrer_hold(void) const { t_mutex_guard guard(mtx_user); return referrer_hold; } bool t_user::get_allow_refer(void) const { t_mutex_guard guard(mtx_user); return allow_refer; } bool t_user::get_ask_user_to_refer(void) const { t_mutex_guard guard(mtx_user); return ask_user_to_refer; } bool t_user::get_auto_refresh_refer_sub(void) const { t_mutex_guard guard(mtx_user); return auto_refresh_refer_sub; } bool t_user::get_attended_refer_to_aor(void) const { t_mutex_guard guard(mtx_user); return attended_refer_to_aor; } bool t_user::get_allow_transfer_consultation_inprog(void) const { t_mutex_guard guard(mtx_user); return allow_transfer_consultation_inprog; } bool t_user::get_send_p_preferred_id(void) const { bool result; mtx_user.lock(); result = send_p_preferred_id; mtx_user.unlock(); return result; } t_sip_transport t_user::get_sip_transport(void) const { t_mutex_guard guard(mtx_user); return sip_transport; } unsigned short t_user::get_sip_transport_udp_threshold(void) const { t_mutex_guard guard(mtx_user); return sip_transport_udp_threshold; } bool t_user::get_use_nat_public_ip(void) const { bool result; mtx_user.lock(); result = use_nat_public_ip; mtx_user.unlock(); return result; } string t_user::get_nat_public_ip(void) const { string result; mtx_user.lock(); result = nat_public_ip; mtx_user.unlock(); return result; } bool t_user::get_use_stun(void) const { bool result; mtx_user.lock(); result = use_stun; mtx_user.unlock(); return result; } t_url t_user::get_stun_server(void) const { t_url result; mtx_user.lock(); result = stun_server; mtx_user.unlock(); return result; } bool t_user::get_persistent_tcp(void) const { t_mutex_guard guard(mtx_user); return persistent_tcp; } bool t_user::get_enable_nat_keepalive(void) const { t_mutex_guard guard(mtx_user); return enable_nat_keepalive; } unsigned short t_user::get_timer_noanswer(void) const { unsigned short result; mtx_user.lock(); result = timer_noanswer; mtx_user.unlock(); return result; } unsigned short t_user::get_timer_nat_keepalive(void) const { unsigned short result; mtx_user.lock(); result = timer_nat_keepalive; mtx_user.unlock(); return result; } unsigned short t_user::get_timer_tcp_ping(void) const { t_mutex_guard guard(mtx_user); return timer_tcp_ping; } bool t_user::get_display_useronly_phone(void) const { bool result; mtx_user.lock(); result = display_useronly_phone; mtx_user.unlock(); return result; } bool t_user::get_numerical_user_is_phone(void) const { bool result; mtx_user.lock(); result = numerical_user_is_phone; mtx_user.unlock(); return result; } bool t_user::get_remove_special_phone_symbols(void) const { bool result; mtx_user.lock(); result = remove_special_phone_symbols; mtx_user.unlock(); return result; } string t_user::get_special_phone_symbols(void) const { string result; mtx_user.lock(); result = special_phone_symbols; mtx_user.unlock(); return result; } bool t_user::get_use_tel_uri_for_phone(void) const { t_mutex_guard guard(mtx_user); return use_tel_uri_for_phone; } string t_user::get_ringtone_file(void) const { string result; mtx_user.lock(); result = ringtone_file; mtx_user.unlock(); return result; } string t_user::get_ringback_file(void) const { string result; mtx_user.lock(); result = ringback_file; mtx_user.unlock(); return result; } string t_user::get_script_incoming_call(void) const { string result; mtx_user.lock(); result = script_incoming_call; mtx_user.unlock(); return result; } string t_user::get_script_in_call_answered(void) const { string result; mtx_user.lock(); result = script_in_call_answered; mtx_user.unlock(); return result; } string t_user::get_script_in_call_failed(void) const { string result; mtx_user.lock(); result = script_in_call_failed; mtx_user.unlock(); return result; } string t_user::get_script_outgoing_call(void) const { string result; mtx_user.lock(); result = script_outgoing_call; mtx_user.unlock(); return result; } string t_user::get_script_out_call_answered(void) const { string result; mtx_user.lock(); result = script_out_call_answered; mtx_user.unlock(); return result; } string t_user::get_script_out_call_failed(void) const { string result; mtx_user.lock(); result = script_out_call_failed; mtx_user.unlock(); return result; } string t_user::get_script_local_release(void) const { string result; mtx_user.lock(); result = script_local_release; mtx_user.unlock(); return result; } string t_user::get_script_remote_release(void) const { string result; mtx_user.lock(); result = script_remote_release; mtx_user.unlock(); return result; } list t_user::get_number_conversions(void) const { list result; mtx_user.lock(); result = number_conversions; mtx_user.unlock(); return result; } bool t_user::get_zrtp_enabled(void) const { bool result; mtx_user.lock(); result = zrtp_enabled; mtx_user.unlock(); return result; } bool t_user::get_zrtp_goclear_warning(void) const { bool result; mtx_user.lock(); result = zrtp_goclear_warning; mtx_user.unlock(); return result; } bool t_user::get_zrtp_sdp(void) const { bool result; mtx_user.lock(); result = zrtp_sdp; mtx_user.unlock(); return result; } bool t_user::get_zrtp_send_if_supported(void) const { bool result; mtx_user.lock(); result = zrtp_send_if_supported; mtx_user.unlock(); return result; } bool t_user::get_mwi_sollicited(void) const { bool result; mtx_user.lock(); result = mwi_sollicited; mtx_user.unlock(); return result; } string t_user::get_mwi_user(void) const { string result; mtx_user.lock(); result = mwi_user; mtx_user.unlock(); return result; } t_url t_user::get_mwi_server(void) const { t_url result; mtx_user.lock(); result = mwi_server; mtx_user.unlock(); return result; } bool t_user::get_mwi_via_proxy(void) const { bool result; mtx_user.lock(); result = mwi_via_proxy; mtx_user.unlock(); return result; } unsigned long t_user::get_mwi_subscription_time(void) const { unsigned long result; mtx_user.lock(); result = mwi_subscription_time; mtx_user.unlock(); return result; } string t_user::get_mwi_vm_address(void) const { string result; mtx_user.lock(); result = mwi_vm_address; mtx_user.unlock(); return result; } unsigned short t_user::get_im_max_sessions(void) const { unsigned short result; mtx_user.lock(); result = im_max_sessions; mtx_user.unlock(); return result; } bool t_user::get_im_send_iscomposing(void) const { t_mutex_guard guard(mtx_user); return im_send_iscomposing; } unsigned long t_user::get_pres_subscription_time(void) const { unsigned long result; mtx_user.lock(); result = pres_subscription_time; mtx_user.unlock(); return result; } unsigned long t_user::get_pres_publication_time(void) const { unsigned long result; mtx_user.lock(); result = pres_publication_time; mtx_user.unlock(); return result; } bool t_user::get_pres_publish_startup(void) const { bool result; mtx_user.lock(); result = pres_publish_startup; mtx_user.unlock(); return result; } void t_user::set_name(const string &_name) { mtx_user.lock(); name = _name; mtx_user.unlock(); } void t_user::set_domain(const string &_domain) { mtx_user.lock(); domain = _domain; mtx_user.unlock(); } void t_user::set_display(const string &_display) { mtx_user.lock(); display = _display; mtx_user.unlock(); } void t_user::set_organization(const string &_organization) { mtx_user.lock(); organization = _organization; mtx_user.unlock(); } void t_user::set_auth_realm(const string &realm) { mtx_user.lock(); auth_realm = realm; mtx_user.unlock(); } void t_user::set_auth_name(const string &name) { mtx_user.lock(); auth_name = name; mtx_user.unlock(); } void t_user::set_auth_pass(const string &pass) { mtx_user.lock(); auth_pass = pass; mtx_user.unlock(); } void t_user::set_auth_aka_op(const uint8 *aka_op) { t_mutex_guard guard(mtx_user); memcpy(auth_aka_op, aka_op, AKA_OPLEN); } void t_user::set_auth_aka_amf(const uint8 *aka_amf) { t_mutex_guard guard(mtx_user); memcpy(auth_aka_amf, aka_amf, AKA_AMFLEN); } void t_user::set_use_outbound_proxy(bool b) { mtx_user.lock(); use_outbound_proxy = b; mtx_user.unlock(); } void t_user::set_outbound_proxy(const t_url &url) { mtx_user.lock(); outbound_proxy = url; mtx_user.unlock(); } void t_user::set_all_requests_to_proxy(bool b) { mtx_user.lock(); all_requests_to_proxy = b; mtx_user.unlock(); } void t_user::set_non_resolvable_to_proxy(bool b) { mtx_user.lock(); non_resolvable_to_proxy = b; mtx_user.unlock(); } void t_user::set_use_registrar(bool b) { mtx_user.lock(); use_registrar = b; mtx_user.unlock(); } void t_user::set_registrar(const t_url &url) { mtx_user.lock(); registrar = url; mtx_user.unlock(); } void t_user::set_registration_time(const unsigned long time) { mtx_user.lock(); registration_time = time; mtx_user.unlock(); } void t_user::set_register_at_startup(bool b) { mtx_user.lock(); register_at_startup = b; mtx_user.unlock(); } void t_user::set_reg_add_qvalue(bool b) { mtx_user.lock(); reg_add_qvalue = b; mtx_user.unlock(); } void t_user::set_reg_qvalue(float q) { mtx_user.lock(); reg_qvalue = q; mtx_user.unlock(); } void t_user::set_codecs(const list &_codecs) { mtx_user.lock(); codecs = _codecs; mtx_user.unlock(); } void t_user::set_ptime(unsigned short _ptime) { mtx_user.lock(); ptime = _ptime; mtx_user.unlock(); } void t_user::set_out_obey_far_end_codec_pref(bool b) { mtx_user.lock(); out_obey_far_end_codec_pref = b; mtx_user.unlock(); } void t_user::set_in_obey_far_end_codec_pref(bool b) { mtx_user.lock(); in_obey_far_end_codec_pref = b; mtx_user.unlock(); } void t_user::set_speex_nb_payload_type(unsigned short payload_type) { mtx_user.lock(); speex_nb_payload_type = payload_type; mtx_user.unlock(); } void t_user::set_speex_wb_payload_type(unsigned short payload_type) { mtx_user.lock(); speex_wb_payload_type = payload_type; mtx_user.unlock(); } void t_user::set_speex_uwb_payload_type(unsigned short payload_type) { mtx_user.lock(); speex_uwb_payload_type = payload_type; mtx_user.unlock(); } void t_user::set_speex_bit_rate_type(t_bit_rate_type bit_rate_type) { mtx_user.lock(); speex_bit_rate_type = bit_rate_type; mtx_user.unlock(); } void t_user::set_speex_abr_nb(int abr) { mtx_user.lock(); speex_abr_nb = abr; mtx_user.unlock(); } void t_user::set_speex_abr_wb(int abr) { mtx_user.lock(); speex_abr_wb = abr; mtx_user.unlock(); } void t_user::set_speex_dtx(bool b) { mtx_user.lock(); speex_dtx = b; mtx_user.unlock(); } void t_user::set_speex_penh(bool b) { mtx_user.lock(); speex_penh = b; mtx_user.unlock(); } void t_user::set_speex_quality(unsigned short quality) { mtx_user.lock(); speex_quality = quality; mtx_user.unlock(); } void t_user::set_speex_complexity(unsigned short complexity) { mtx_user.lock(); speex_complexity = complexity; mtx_user.unlock(); } void t_user::set_speex_dsp_vad(bool b) { mtx_user.lock(); speex_dsp_vad = b; mtx_user.unlock(); } void t_user::set_speex_dsp_agc(bool b) { mtx_user.lock(); speex_dsp_agc = b; mtx_user.unlock(); } void t_user::set_speex_dsp_agc_level(unsigned short level) { mtx_user.lock(); speex_dsp_agc_level = level; mtx_user.unlock(); } void t_user::set_speex_dsp_aec(bool b) { mtx_user.lock(); speex_dsp_aec = b; mtx_user.unlock(); } void t_user::set_speex_dsp_nrd(bool b) { mtx_user.lock(); speex_dsp_nrd = b; mtx_user.unlock(); } void t_user::set_ilbc_payload_type(unsigned short payload_type) { mtx_user.lock(); ilbc_payload_type = payload_type; mtx_user.unlock(); } void t_user::set_ilbc_mode(unsigned short mode) { mtx_user.lock(); ilbc_mode = mode; mtx_user.unlock(); } void t_user::set_g726_16_payload_type(unsigned short payload_type) { mtx_user.lock(); g726_16_payload_type = payload_type; mtx_user.unlock(); } void t_user::set_g726_24_payload_type(unsigned short payload_type) { mtx_user.lock(); g726_24_payload_type = payload_type; mtx_user.unlock(); } void t_user::set_g726_32_payload_type(unsigned short payload_type) { mtx_user.lock(); g726_32_payload_type = payload_type; mtx_user.unlock(); } void t_user::set_g726_40_payload_type(unsigned short payload_type) { mtx_user.lock(); g726_40_payload_type = payload_type; mtx_user.unlock(); } void t_user::set_g726_packing(t_g726_packing packing) { mtx_user.lock(); g726_packing = packing; mtx_user.unlock(); } void t_user::set_dtmf_transport(t_dtmf_transport _dtmf_transport) { mtx_user.lock(); dtmf_transport = _dtmf_transport; mtx_user.unlock(); } void t_user::set_dtmf_payload_type(unsigned short payload_type) { mtx_user.lock(); dtmf_payload_type = payload_type; mtx_user.unlock(); } void t_user::set_dtmf_duration(unsigned short duration) { mtx_user.lock(); dtmf_duration = duration; mtx_user.unlock(); } void t_user::set_dtmf_pause(unsigned short pause) { mtx_user.lock(); dtmf_pause = pause; mtx_user.unlock(); } void t_user::set_dtmf_volume(unsigned short volume) { mtx_user.lock(); dtmf_volume = volume; mtx_user.unlock(); } void t_user::set_hold_variant(t_hold_variant _hold_variant) { mtx_user.lock(); hold_variant = _hold_variant; mtx_user.unlock(); } void t_user::set_check_max_forwards(bool b) { mtx_user.lock(); check_max_forwards = b; mtx_user.unlock(); } void t_user::set_allow_missing_contact_reg(bool b) { mtx_user.lock(); allow_missing_contact_reg = b; mtx_user.unlock(); } void t_user::set_registration_time_in_contact(bool b) { mtx_user.lock(); registration_time_in_contact = b; mtx_user.unlock(); } void t_user::set_compact_headers(bool b) { mtx_user.lock(); compact_headers = b; mtx_user.unlock(); } void t_user::set_encode_multi_values_as_list(bool b) { mtx_user.lock(); encode_multi_values_as_list = b; mtx_user.unlock(); } void t_user::set_use_domain_in_contact(bool b) { mtx_user.lock(); use_domain_in_contact = b; mtx_user.unlock(); } void t_user::set_allow_sdp_change(bool b) { mtx_user.lock(); allow_sdp_change = b; mtx_user.unlock(); } void t_user::set_allow_redirection(bool b) { mtx_user.lock(); allow_redirection = b; mtx_user.unlock(); } void t_user::set_ask_user_to_redirect(bool b) { mtx_user.lock(); ask_user_to_redirect = b; mtx_user.unlock(); } void t_user::set_max_redirections(unsigned short _max_redirections) { mtx_user.lock(); max_redirections = _max_redirections; mtx_user.unlock(); } void t_user::set_ext_100rel(t_ext_support ext_support) { mtx_user.lock(); ext_100rel = ext_support; mtx_user.unlock(); } void t_user::set_ext_replaces(bool b) { mtx_user.lock(); ext_replaces = b; mtx_user.unlock(); } void t_user::set_referee_hold(bool b) { t_mutex_guard guard(mtx_user); referee_hold = b; } void t_user::set_referrer_hold(bool b) { mtx_user.lock(); referrer_hold = b; mtx_user.unlock(); } void t_user::set_allow_refer(bool b) { t_mutex_guard guard(mtx_user); allow_refer = b; } void t_user::set_ask_user_to_refer(bool b) { t_mutex_guard guard(mtx_user); ask_user_to_refer = b; } void t_user::set_auto_refresh_refer_sub(bool b) { t_mutex_guard guard(mtx_user); auto_refresh_refer_sub = b; } void t_user::set_attended_refer_to_aor(bool b) { t_mutex_guard guard(mtx_user); attended_refer_to_aor = b; } void t_user::set_allow_transfer_consultation_inprog(bool b) { t_mutex_guard guard(mtx_user); allow_transfer_consultation_inprog = b; } void t_user::set_send_p_preferred_id(bool b) { mtx_user.lock(); send_p_preferred_id = b; mtx_user.unlock(); } void t_user::set_sip_transport(t_sip_transport transport) { t_mutex_guard guard(mtx_user); sip_transport = transport; } void t_user::set_sip_transport_udp_threshold(unsigned short threshold) { t_mutex_guard guard(mtx_user); sip_transport_udp_threshold = threshold; } void t_user::set_use_nat_public_ip(bool b) { mtx_user.lock(); use_nat_public_ip = b; mtx_user.unlock(); } void t_user::set_nat_public_ip(const string &public_ip) { mtx_user.lock(); nat_public_ip = public_ip; mtx_user.unlock(); } void t_user::set_use_stun(bool b) { mtx_user.lock(); use_stun = b; mtx_user.unlock(); } void t_user::set_stun_server(const t_url &url) { mtx_user.lock(); stun_server = url; mtx_user.unlock(); } void t_user::set_persistent_tcp(bool b) { t_mutex_guard guard(mtx_user); persistent_tcp = b; } void t_user::set_enable_nat_keepalive(bool b) { t_mutex_guard guard(mtx_user); enable_nat_keepalive = b; } void t_user::set_timer_noanswer(unsigned short timer) { mtx_user.lock(); timer_noanswer = timer; mtx_user.unlock(); } void t_user::set_timer_nat_keepalive(unsigned short timer) { mtx_user.lock(); timer_nat_keepalive = timer; mtx_user.unlock(); } void t_user::set_timer_tcp_ping(unsigned short timer) { t_mutex_guard guard(mtx_user); timer_tcp_ping = timer; } void t_user::set_display_useronly_phone(bool b) { mtx_user.lock(); display_useronly_phone = b; mtx_user.unlock(); } void t_user::set_numerical_user_is_phone(bool b) { mtx_user.lock(); numerical_user_is_phone = b; mtx_user.unlock(); } void t_user::set_remove_special_phone_symbols(bool b) { mtx_user.lock(); remove_special_phone_symbols = b; mtx_user.unlock(); } void t_user::set_special_phone_symbols(const string &symbols) { mtx_user.lock(); special_phone_symbols = symbols; mtx_user.unlock(); } void t_user::set_use_tel_uri_for_phone(bool b) { t_mutex_guard guard(mtx_user); use_tel_uri_for_phone = b; } void t_user::set_ringtone_file(const string &file) { mtx_user.lock(); ringtone_file = file; mtx_user.unlock(); } void t_user::set_ringback_file(const string &file) { mtx_user.lock(); ringback_file = file; mtx_user.unlock(); } void t_user::set_script_incoming_call(const string &script) { mtx_user.lock(); script_incoming_call = script; mtx_user.unlock(); } void t_user::set_script_in_call_answered(const string &script) { mtx_user.lock(); script_in_call_answered = script; mtx_user.unlock(); } void t_user::set_script_in_call_failed(const string &script) { mtx_user.lock(); script_in_call_failed = script; mtx_user.unlock(); } void t_user::set_script_outgoing_call(const string &script) { mtx_user.lock(); script_outgoing_call = script; mtx_user.unlock(); } void t_user::set_script_out_call_answered(const string &script) { mtx_user.lock(); script_out_call_answered = script; mtx_user.unlock(); } void t_user::set_script_out_call_failed(const string &script) { mtx_user.lock(); script_out_call_failed = script; mtx_user.unlock(); } void t_user::set_script_local_release(const string &script) { mtx_user.lock(); script_local_release = script; mtx_user.unlock(); } void t_user::set_script_remote_release(const string &script) { mtx_user.lock(); script_remote_release = script; mtx_user.unlock(); } void t_user::set_number_conversions(const list &l) { mtx_user.lock(); number_conversions = l; mtx_user.unlock(); } void t_user::set_zrtp_enabled(bool b) { mtx_user.lock(); zrtp_enabled = b; mtx_user.unlock(); } void t_user::set_zrtp_goclear_warning(bool b) { mtx_user.lock(); zrtp_goclear_warning = b; mtx_user.unlock(); } void t_user::set_zrtp_sdp(bool b) { mtx_user.lock(); zrtp_sdp = b; mtx_user.unlock(); } void t_user::set_zrtp_send_if_supported(bool b) { mtx_user.lock(); zrtp_send_if_supported = b; mtx_user.unlock(); } void t_user::set_mwi_sollicited(bool b) { mtx_user.lock(); mwi_sollicited = b; mtx_user.unlock(); } void t_user::set_mwi_user(const string &user) { mtx_user.lock(); mwi_user = user; mtx_user.unlock(); } void t_user::set_mwi_server(const t_url &url) { mtx_user.lock(); mwi_server = url; mtx_user.unlock(); } void t_user::set_mwi_via_proxy(bool b) { mtx_user.lock(); mwi_via_proxy = b; mtx_user.unlock(); } void t_user::set_mwi_subscription_time(unsigned long t) { mtx_user.lock(); mwi_subscription_time = t; mtx_user.unlock(); } void t_user::set_mwi_vm_address(const string &address) { mtx_user.lock(); mwi_vm_address = address; mtx_user.unlock(); } void t_user::set_im_max_sessions(unsigned short max_sessions) { mtx_user.lock(); im_max_sessions = max_sessions; mtx_user.unlock(); } void t_user::set_im_send_iscomposing(bool b) { t_mutex_guard guard(mtx_user); im_send_iscomposing = b; } void t_user::set_pres_subscription_time(unsigned long t) { mtx_user.lock(); pres_subscription_time = t; mtx_user.unlock(); } void t_user::set_pres_publication_time(unsigned long t) { mtx_user.lock(); pres_publication_time = t; mtx_user.unlock(); } void t_user::set_pres_publish_startup(bool b) { mtx_user.lock(); pres_publish_startup = b; mtx_user.unlock(); } bool t_user::read_config(const string &filename, string &error_msg) { string f; string msg; mtx_user.lock(); if (filename.size() == 0) { error_msg = "Cannot read user profile: missing file name."; log_file->write_report(error_msg, "t_user::read_config", LOG_NORMAL, LOG_CRITICAL); mtx_user.unlock(); return false; } config_filename = filename; f = expand_filename(filename); ifstream config(f.c_str()); if (!config) { error_msg = "Cannot open file for reading: "; error_msg += f; log_file->write_report(error_msg, "t_user::read_config", LOG_NORMAL, LOG_CRITICAL); mtx_user.unlock(); return false; } log_file->write_header("t_user::read_config"); log_file->write_raw("Reading config: "); log_file->write_raw(filename); log_file->write_endl(); log_file->write_footer(); while (!config.eof()) { string line; getline(config, line); // Check if read operation succeeded if (!config.good() && !config.eof()) { error_msg = "File system error while reading file "; error_msg += f; log_file->write_report(error_msg, "t_user::read_config", LOG_NORMAL, LOG_CRITICAL); mtx_user.unlock(); return false; } line = trim(line); // Skip empty lines if (line.size() == 0) continue; // Skip comment lines if (line[0] == '#') continue; vector l = split_on_first(line, '='); if (l.size() != 2) { error_msg = "Syntax error in file "; error_msg += f; error_msg += "\n"; error_msg += line; log_file->write_report(error_msg, "t_user::read_config", LOG_NORMAL, LOG_CRITICAL); mtx_user.unlock(); return false; } string parameter = trim(l[0]); string value = trim(l[1]); if (parameter == FLD_NAME) { name = value; } else if (parameter == FLD_DOMAIN) { domain = value; } else if (parameter == FLD_DISPLAY) { display = value; } else if (parameter == FLD_ORGANIZATION) { organization = value; } else if (parameter == FLD_REGISTRATION_TIME) { registration_time = atol(value.c_str()); } else if (parameter == FLD_REGISTRATION_TIME_IN_CONTACT) { registration_time_in_contact = yesno2bool(value); } else if (parameter == FLD_REGISTRAR) { use_registrar = set_server_value(registrar, USER_SCHEME, value); } else if (parameter == FLD_REGISTER_AT_STARTUP) { register_at_startup = yesno2bool(value); } else if (parameter == FLD_REG_ADD_QVALUE) { reg_add_qvalue = yesno2bool(value); } else if (parameter == FLD_REG_QVALUE) { reg_qvalue = atof(value.c_str()); } else if (parameter == FLD_OUTBOUND_PROXY) { use_outbound_proxy = set_server_value(outbound_proxy, USER_SCHEME, value); } else if (parameter == FLD_ALL_REQUESTS_TO_PROXY) { all_requests_to_proxy = yesno2bool(value); } else if (parameter == FLD_NON_RESOLVABLE_TO_PROXY) { non_resolvable_to_proxy = yesno2bool(value); } else if (parameter == FLD_AUTH_REALM) { auth_realm = value; } else if (parameter == FLD_AUTH_NAME) { auth_name = value; } else if (parameter == FLD_AUTH_PASS) { auth_pass = value; } else if (parameter == FLD_AUTH_AKA_OP) { hex2binary(value, auth_aka_op); } else if (parameter == FLD_AUTH_AKA_AMF) { hex2binary(value, auth_aka_amf); } else if (parameter == FLD_CODECS) { vector l = split(value, ','); if (l.size() > 0) codecs.clear(); for (vector::iterator i = l.begin(); i != l.end(); i++) { string codec = trim(*i); if (codec == "g711a") { codecs.push_back(CODEC_G711_ALAW); } else if (codec == "g711u") { codecs.push_back(CODEC_G711_ULAW); } else if (codec == "gsm") { codecs.push_back(CODEC_GSM); #ifdef HAVE_SPEEX } else if (codec == "speex-nb") { codecs.push_back(CODEC_SPEEX_NB); } else if (codec == "speex-wb") { codecs.push_back(CODEC_SPEEX_WB); } else if (codec == "speex-uwb") { codecs.push_back(CODEC_SPEEX_UWB); #endif #ifdef HAVE_ILBC } else if (codec == "ilbc") { codecs.push_back(CODEC_ILBC); #endif } else if (codec == "g726-16") { codecs.push_back(CODEC_G726_16); } else if (codec == "g726-24") { codecs.push_back(CODEC_G726_24); } else if (codec == "g726-32") { codecs.push_back(CODEC_G726_32); } else if (codec == "g726-40") { codecs.push_back(CODEC_G726_40); #ifdef HAVE_BCG729 } else if (codec == "g729a") { codecs.push_back(CODEC_G729A); #endif } else { msg = "Syntax error in file "; msg += f; msg += "\n"; msg += "Invalid codec: "; msg += value; log_file->write_report(msg, "t_user::read_config", LOG_NORMAL, LOG_WARNING); } } } else if (parameter == FLD_PTIME) { ptime = atoi(value.c_str()); } else if (parameter == FLD_OUT_FAR_END_CODEC_PREF) { out_obey_far_end_codec_pref = yesno2bool(value); } else if (parameter == FLD_IN_FAR_END_CODEC_PREF) { in_obey_far_end_codec_pref = yesno2bool(value); } else if (parameter == FLD_HOLD_VARIANT) { if (value == "rfc2543") { hold_variant = HOLD_RFC2543; } else if (value == "rfc3264") { hold_variant = HOLD_RFC3264; } else { error_msg = "Syntax error in file "; error_msg += f; error_msg += "\n"; error_msg += "Invalid hold variant: "; error_msg += value; log_file->write_report(error_msg, "t_user::read_config", LOG_NORMAL, LOG_CRITICAL); mtx_user.unlock(); return false; } } else if (parameter == FLD_CHECK_MAX_FORWARDS) { check_max_forwards = yesno2bool(value); } else if (parameter == FLD_ALLOW_MISSING_CONTACT_REG) { allow_missing_contact_reg = yesno2bool(value); } else if (parameter == FLD_USE_DOMAIN_IN_CONTACT) { use_domain_in_contact = yesno2bool(value); } else if (parameter == FLD_ALLOW_SDP_CHANGE) { allow_sdp_change = yesno2bool(value); } else if (parameter == FLD_ALLOW_REDIRECTION) { allow_redirection = yesno2bool(value); } else if (parameter == FLD_ASK_USER_TO_REDIRECT) { ask_user_to_redirect = yesno2bool(value); } else if (parameter == FLD_MAX_REDIRECTIONS) { max_redirections = atoi(value.c_str()); } else if (parameter == FLD_REFEREE_HOLD) { referee_hold = yesno2bool(value); } else if (parameter == FLD_REFERRER_HOLD) { referrer_hold = yesno2bool(value); } else if (parameter == FLD_ALLOW_REFER) { allow_refer = yesno2bool(value); } else if (parameter == FLD_ASK_USER_TO_REFER) { ask_user_to_refer = yesno2bool(value); } else if (parameter == FLD_AUTO_REFRESH_REFER_SUB) { auto_refresh_refer_sub = yesno2bool(value); } else if (parameter == FLD_ATTENDED_REFER_TO_AOR) { attended_refer_to_aor = yesno2bool(value); } else if (parameter == FLD_ALLOW_XFER_CONSULT_INPROG) { allow_transfer_consultation_inprog = yesno2bool(value); } else if (parameter == FLD_SEND_P_PREFERRED_ID) { send_p_preferred_id = yesno2bool(value); } else if (parameter == FLD_SIP_TRANSPORT) { sip_transport = str2sip_transport(value); } else if (parameter == FLD_SIP_TRANSPORT_UDP_THRESHOLD) { sip_transport_udp_threshold = atoi(value.c_str()); } else if (parameter == FLD_NAT_PUBLIC_IP) { if (value.size() == 0) continue; use_nat_public_ip = true; nat_public_ip = value; } else if (parameter == FLD_STUN_SERVER) { use_stun = set_server_value(stun_server, "stun", value); } else if (parameter == FLD_PERSISTENT_TCP) { persistent_tcp = yesno2bool(value); } else if (parameter == FLD_ENABLE_NAT_KEEPALIVE) { enable_nat_keepalive = yesno2bool(value); } else if (parameter == FLD_TIMER_NOANSWER) { timer_noanswer = atoi(value.c_str()); } else if (parameter == FLD_TIMER_NAT_KEEPALIVE) { timer_nat_keepalive = atoi(value.c_str()); } else if (parameter == FLD_TIMER_TCP_PING) { timer_tcp_ping = atoi(value.c_str()); } else if (parameter == FLD_EXT_100REL) { ext_100rel = str2ext_support(value); if (ext_100rel == EXT_INVALID) { error_msg = "Syntax error in file "; error_msg += f; error_msg += "\n"; error_msg += "Invalid value for ext_100rel: "; error_msg += value; log_file->write_report(error_msg, "t_user::read_config", LOG_NORMAL, LOG_CRITICAL); mtx_user.unlock(); return false; } } else if (parameter == FLD_EXT_REPLACES) { ext_replaces = yesno2bool(value); } else if (parameter == FLD_COMPACT_HEADERS) { compact_headers = yesno2bool(value); } else if (parameter == FLD_ENCODE_MULTI_VALUES_AS_LIST) { encode_multi_values_as_list = yesno2bool(value); } else if (parameter == FLD_SPEEX_NB_PAYLOAD_TYPE) { speex_nb_payload_type = atoi(value.c_str()); } else if (parameter == FLD_SPEEX_WB_PAYLOAD_TYPE) { speex_wb_payload_type = atoi(value.c_str()); } else if (parameter == FLD_SPEEX_UWB_PAYLOAD_TYPE) { speex_uwb_payload_type = atoi(value.c_str()); } else if (parameter == FLD_SPEEX_BIT_RATE_TYPE) { speex_bit_rate_type = str2bit_rate_type(value); if (speex_bit_rate_type == BIT_RATE_INVALID) { error_msg = "Syntax error in file "; error_msg += f; error_msg += "\n"; error_msg += "Invalid value for speex bit rate type: "; error_msg += value; log_file->write_report(error_msg, "t_user::read_config", LOG_NORMAL, LOG_CRITICAL); mtx_user.unlock(); return false; } } else if (parameter == FLD_SPEEX_ABR_NB) { speex_abr_nb = atoi(value.c_str()); } else if (parameter == FLD_SPEEX_ABR_WB) { speex_abr_wb = atoi(value.c_str()); } else if (parameter == FLD_SPEEX_DTX) { speex_dtx = yesno2bool(value); } else if (parameter == FLD_SPEEX_PENH) { speex_penh = yesno2bool(value); } else if (parameter == FLD_SPEEX_QUALITY) { speex_quality = atoi(value.c_str()); if (speex_quality > 10) { error_msg = "Syntax error in file "; error_msg += f; error_msg += "\n"; error_msg += "Invalid value for speex quality: "; error_msg += value; log_file->write_report(error_msg, "t_user::read_config", LOG_NORMAL, LOG_CRITICAL); mtx_user.unlock(); return false; } } else if (parameter == FLD_SPEEX_COMPLEXITY) { speex_complexity = atoi(value.c_str()); if (speex_complexity < 1 || speex_complexity > 10) { error_msg = "Syntax error in file "; error_msg += f; error_msg += "\n"; error_msg += "Invalid value for speex complexity: "; error_msg += value; log_file->write_report(error_msg, "t_user::read_config", LOG_NORMAL, LOG_CRITICAL); mtx_user.unlock(); return false; } } else if (parameter == FLD_SPEEX_DSP_VAD) { speex_dsp_vad = yesno2bool(value); } else if (parameter == FLD_SPEEX_DSP_AGC) { speex_dsp_agc = yesno2bool(value); } else if (parameter == FLD_SPEEX_DSP_AEC) { speex_dsp_aec = yesno2bool(value); } else if (parameter == FLD_SPEEX_DSP_NRD) { speex_dsp_nrd = yesno2bool(value); } else if (parameter == FLD_SPEEX_DSP_AGC_LEVEL) { speex_dsp_agc_level = atoi(value.c_str()); if (speex_dsp_agc_level < 1 || speex_dsp_agc_level > 100) { error_msg = "Syntax error in file "; error_msg += f; error_msg += "\n"; error_msg += "Invalid value for automatic gain control level: "; error_msg += value; log_file->write_report(error_msg, "t_user::read_config", LOG_NORMAL, LOG_CRITICAL); mtx_user.unlock(); return false; } } else if (parameter == FLD_ILBC_PAYLOAD_TYPE) { ilbc_payload_type = atoi(value.c_str()); } else if (parameter == FLD_ILBC_MODE) { ilbc_mode = atoi(value.c_str()); } else if (parameter == FLD_G726_16_PAYLOAD_TYPE) { g726_16_payload_type = atoi(value.c_str()); } else if (parameter == FLD_G726_24_PAYLOAD_TYPE) { g726_24_payload_type = atoi(value.c_str()); } else if (parameter == FLD_G726_32_PAYLOAD_TYPE) { g726_32_payload_type = atoi(value.c_str()); } else if (parameter == FLD_G726_40_PAYLOAD_TYPE) { g726_40_payload_type = atoi(value.c_str()); } else if (parameter == FLD_G726_PACKING) { g726_packing = str2g726_packing(value); } else if (parameter == FLD_DTMF_TRANSPORT) { dtmf_transport = str2dtmf_transport(value); } else if (parameter == FLD_DTMF_PAYLOAD_TYPE) { dtmf_payload_type = atoi(value.c_str()); } else if (parameter == FLD_DTMF_DURATION) { dtmf_duration = atoi(value.c_str()); } else if (parameter == FLD_DTMF_PAUSE) { dtmf_pause = atoi(value.c_str()); } else if (parameter == FLD_DTMF_VOLUME) { dtmf_volume = atoi(value.c_str()); } else if (parameter == FLD_DISPLAY_USERONLY_PHONE) { display_useronly_phone = yesno2bool(value); } else if (parameter == FLD_NUMERICAL_USER_IS_PHONE) { numerical_user_is_phone = yesno2bool(value); } else if (parameter == FLD_REMOVE_SPECIAL_PHONE_SYM) { remove_special_phone_symbols = yesno2bool(value); } else if (parameter == FLD_SPECIAL_PHONE_SYMBOLS) { special_phone_symbols = value; } else if (parameter == FLD_USE_TEL_URI_FOR_PHONE) { use_tel_uri_for_phone = yesno2bool(value); } else if (parameter == FLD_USER_RINGTONE_FILE) { ringtone_file = value; } else if (parameter == FLD_USER_RINGBACK_FILE) { ringback_file = value; } else if (parameter == FLD_SCRIPT_INCOMING_CALL) { script_incoming_call = value; } else if (parameter == FLD_SCRIPT_IN_CALL_ANSWERED) { script_in_call_answered = value; } else if (parameter == FLD_SCRIPT_IN_CALL_FAILED) { script_in_call_failed = value; } else if (parameter == FLD_SCRIPT_OUTGOING_CALL) { script_outgoing_call = value; } else if (parameter == FLD_SCRIPT_OUT_CALL_ANSWERED) { script_out_call_answered = value; } else if (parameter == FLD_SCRIPT_OUT_CALL_FAILED) { script_out_call_failed = value; } else if (parameter == FLD_SCRIPT_LOCAL_RELEASE) { script_local_release = value; } else if (parameter == FLD_SCRIPT_REMOTE_RELEASE) { script_remote_release = value; } else if (parameter == FLD_NUMBER_CONVERSION) { t_number_conversion c; if (parse_num_conversion(value, c)) { number_conversions.push_back(c); } } else if (parameter == FLD_ZRTP_ENABLED) { zrtp_enabled = yesno2bool(value); } else if (parameter == FLD_ZRTP_GOCLEAR_WARNING) { zrtp_goclear_warning = yesno2bool(value); } else if (parameter == FLD_ZRTP_SDP) { zrtp_sdp = yesno2bool(value); } else if (parameter == FLD_ZRTP_SEND_IF_SUPPORTED) { zrtp_send_if_supported = yesno2bool(value); } else if (parameter == FLD_MWI_SOLLICITED) { mwi_sollicited = yesno2bool(value); } else if (parameter == FLD_MWI_USER) { mwi_user = value; } else if (parameter == FLD_MWI_SERVER) { (void)set_server_value(mwi_server, USER_SCHEME, value); } else if (parameter == FLD_MWI_VIA_PROXY) { mwi_via_proxy = yesno2bool(value); } else if (parameter == FLD_MWI_SUBSCRIPTION_TIME) { mwi_subscription_time = atol(value.c_str()); } else if (parameter == FLD_MWI_VM_ADDRESS) { mwi_vm_address = value; } else if (parameter == FLD_IM_MAX_SESSIONS) { im_max_sessions = atoi(value.c_str()); } else if (parameter == FLD_IM_SEND_ISCOMPOSING) { im_send_iscomposing = yesno2bool(value); } else if (parameter == FLD_PRES_SUBSCRIPTION_TIME) { pres_subscription_time = atol(value.c_str()); } else if (parameter == FLD_PRES_PUBLICATION_TIME) { pres_publication_time = atol(value.c_str()); } else if (parameter == FLD_PRES_PUBLISH_STARTUP) { pres_publish_startup = yesno2bool(value); } else { // Ignore unknown parameters. Only report in log file. log_file->write_header("t_user::read_config", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Unknown parameter in user profile: "); log_file->write_raw(parameter); log_file->write_endl(); log_file->write_footer(); } } // Set parser options t_parser::check_max_forwards = check_max_forwards; t_parser::compact_headers = compact_headers; t_parser::multi_values_as_list = encode_multi_values_as_list; mtx_user.unlock(); return true; } bool t_user::write_config(const string &filename, string &error_msg) { struct stat stat_buf; string f; mtx_user.lock(); if (filename.size() == 0) { error_msg = "Cannot write user profile: missing file name."; log_file->write_report(error_msg, "t_user::write_config", LOG_NORMAL, LOG_CRITICAL); mtx_user.unlock(); return false; } config_filename = filename; f = expand_filename(filename); // Make a backup of the file if we are editing an existing file, so // that can be restored when writing fails. string f_backup = f + '~'; if (stat(f.c_str(), &stat_buf) == 0) { if (rename(f.c_str(), f_backup.c_str()) != 0) { string err = get_error_str(errno); error_msg = "Failed to backup "; error_msg += f; error_msg += " to "; error_msg += f_backup; error_msg += "\n"; error_msg += err; log_file->write_report(error_msg, "t_user::write_config", LOG_NORMAL, LOG_CRITICAL); mtx_user.unlock(); return false; } } ofstream config(f.c_str()); if (!config) { error_msg = "Cannot open file for writing: "; error_msg += f; log_file->write_report(error_msg, "t_user::write_config", LOG_NORMAL, LOG_CRITICAL); mtx_user.unlock(); return false; } log_file->write_header("t_user::write_config"); log_file->write_raw("Writing config: "); log_file->write_raw(filename); log_file->write_endl(); log_file->write_footer(); // Write USER settings config << "# USER\n"; config << FLD_NAME << '=' << name << endl; config << FLD_DOMAIN << '=' << domain << endl; config << FLD_DISPLAY << '=' << display << endl; config << FLD_ORGANIZATION << '=' << organization << endl; config << FLD_AUTH_REALM << '=' << auth_realm << endl; config << FLD_AUTH_NAME << '=' << auth_name << endl; config << FLD_AUTH_PASS << '=' << auth_pass << endl; config << FLD_AUTH_AKA_OP << '=' << binary2hex(auth_aka_op, AKA_OPLEN) << endl; config << FLD_AUTH_AKA_AMF << '=' << binary2hex(auth_aka_amf, AKA_AMFLEN) << endl; config << endl; // Write SIP SERVER settings config << "# SIP SERVER\n"; if (use_outbound_proxy) { config << FLD_OUTBOUND_PROXY << '='; config << outbound_proxy.encode_noscheme() << endl; config << FLD_ALL_REQUESTS_TO_PROXY << '='; config << bool2yesno(all_requests_to_proxy) << endl; config << FLD_NON_RESOLVABLE_TO_PROXY << '='; config << bool2yesno(non_resolvable_to_proxy) << endl; } else { config << FLD_OUTBOUND_PROXY << '=' << endl; config << FLD_ALL_REQUESTS_TO_PROXY << "=no" << endl; } if (use_registrar) { config << FLD_REGISTRAR << '=' << registrar.encode_noscheme(); config << endl; } else { config << FLD_REGISTRAR << '=' << endl; } config << FLD_REGISTER_AT_STARTUP << '='; config << bool2yesno(register_at_startup) << endl; config << FLD_REGISTRATION_TIME << '=' << registration_time << endl; config << FLD_REG_ADD_QVALUE << '=' << bool2yesno(reg_add_qvalue) << endl; config << FLD_REG_QVALUE << '=' << reg_qvalue << endl; config << endl; // Write AUDIO settings config << "# RTP AUDIO\n"; config << FLD_CODECS << '='; for (list::iterator i = codecs.begin(); i != codecs.end(); i++) { if (i != codecs.begin()) config << ','; switch(*i) { case CODEC_G711_ALAW: config << "g711a"; break; case CODEC_G711_ULAW: config << "g711u"; break; case CODEC_GSM: config << "gsm"; break; case CODEC_SPEEX_NB: config << "speex-nb"; break; case CODEC_SPEEX_WB: config << "speex-wb"; break; case CODEC_SPEEX_UWB: config << "speex-uwb"; break; case CODEC_ILBC: config << "ilbc"; break; case CODEC_G726_16: config << "g726-16"; break; case CODEC_G726_24: config << "g726-24"; break; case CODEC_G726_32: config << "g726-32"; break; case CODEC_G726_40: config << "g726-40"; break; case CODEC_G729A: config << "g729a"; break; default: assert(false); } } config << endl; config << FLD_PTIME << '=' << ptime << endl; config << FLD_OUT_FAR_END_CODEC_PREF << '=' << bool2yesno(out_obey_far_end_codec_pref) << endl; config << FLD_IN_FAR_END_CODEC_PREF << '=' << bool2yesno(in_obey_far_end_codec_pref) << endl; config << FLD_SPEEX_NB_PAYLOAD_TYPE << '=' << speex_nb_payload_type << endl; config << FLD_SPEEX_WB_PAYLOAD_TYPE << '=' << speex_wb_payload_type << endl; config << FLD_SPEEX_UWB_PAYLOAD_TYPE << '=' << speex_uwb_payload_type << endl; config << FLD_SPEEX_BIT_RATE_TYPE << '='; // config << FLD_SPEEX_ABR_NB << '=' << speex_abr_nb << endl; // config << FLD_SPEEX_ABR_WB << '=' << speex_abr_wb << endl; config << bit_rate_type2str(speex_bit_rate_type) << endl; config << FLD_SPEEX_DTX << '=' << bool2yesno(speex_dtx) << endl; config << FLD_SPEEX_PENH << '=' << bool2yesno(speex_penh) << endl; config << FLD_SPEEX_QUALITY << '=' << speex_quality << endl; config << FLD_SPEEX_COMPLEXITY << '=' << speex_complexity << endl; config << FLD_SPEEX_DSP_VAD << '=' << bool2yesno(speex_dsp_vad) << endl; config << FLD_SPEEX_DSP_AGC << '=' << bool2yesno(speex_dsp_agc) << endl; config << FLD_SPEEX_DSP_AEC << '=' << bool2yesno(speex_dsp_aec) << endl; config << FLD_SPEEX_DSP_NRD << '=' << bool2yesno(speex_dsp_nrd) << endl; config << FLD_SPEEX_DSP_AGC_LEVEL << '=' << speex_dsp_agc_level << endl; config << FLD_ILBC_PAYLOAD_TYPE << '=' << ilbc_payload_type << endl; config << FLD_ILBC_MODE << '=' << ilbc_mode << endl; config << FLD_G726_16_PAYLOAD_TYPE << '=' << g726_16_payload_type << endl; config << FLD_G726_24_PAYLOAD_TYPE << '=' << g726_24_payload_type << endl; config << FLD_G726_32_PAYLOAD_TYPE << '=' << g726_32_payload_type << endl; config << FLD_G726_40_PAYLOAD_TYPE << '=' << g726_40_payload_type << endl; config << FLD_G726_PACKING << '=' << g726_packing2str(g726_packing) << endl; config << FLD_DTMF_TRANSPORT << '=' << dtmf_transport2str(dtmf_transport) << endl; config << FLD_DTMF_PAYLOAD_TYPE << '=' << dtmf_payload_type << endl; config << FLD_DTMF_DURATION << '=' << dtmf_duration << endl; config << FLD_DTMF_PAUSE << '=' << dtmf_pause << endl; config << FLD_DTMF_VOLUME << '=' << dtmf_volume << endl; config << endl; // Write SIP PROTOCOL settings config << "# SIP PROTOCOL\n"; config << FLD_HOLD_VARIANT << '='; switch(hold_variant) { case HOLD_RFC2543: config << "rfc2543"; break; case HOLD_RFC3264: config << "rfc3264"; break; default: assert(false); } config << endl; config << FLD_CHECK_MAX_FORWARDS << '='; config << bool2yesno(check_max_forwards) << endl; config << FLD_ALLOW_MISSING_CONTACT_REG << '='; config << bool2yesno(allow_missing_contact_reg) << endl; config << FLD_REGISTRATION_TIME_IN_CONTACT << '='; config << bool2yesno(registration_time_in_contact) << endl; config << FLD_COMPACT_HEADERS << '=' << bool2yesno(compact_headers) << endl; config << FLD_ENCODE_MULTI_VALUES_AS_LIST << '='; config << bool2yesno(encode_multi_values_as_list) << endl; config << FLD_USE_DOMAIN_IN_CONTACT << '='; config << bool2yesno(use_domain_in_contact) << endl; config << FLD_ALLOW_SDP_CHANGE << '=' << bool2yesno(allow_sdp_change) << endl; config << FLD_ALLOW_REDIRECTION << '=' << bool2yesno(allow_redirection); config << endl; config << FLD_ASK_USER_TO_REDIRECT << '='; config << bool2yesno(ask_user_to_redirect) << endl; config << FLD_MAX_REDIRECTIONS << '=' << max_redirections << endl; config << FLD_EXT_100REL << '=' << ext_support2str(ext_100rel) << endl; config << FLD_EXT_REPLACES << '=' << bool2yesno(ext_replaces) << endl; config << FLD_REFEREE_HOLD << '=' << bool2yesno(referee_hold) << endl; config << FLD_REFERRER_HOLD << '=' << bool2yesno(referrer_hold) << endl; config << FLD_ALLOW_REFER << '=' << bool2yesno(allow_refer) << endl; config << FLD_ASK_USER_TO_REFER << '='; config << bool2yesno(ask_user_to_refer) << endl; config << FLD_AUTO_REFRESH_REFER_SUB << '='; config << bool2yesno(auto_refresh_refer_sub) << endl; config << FLD_ATTENDED_REFER_TO_AOR << '='; config << bool2yesno(attended_refer_to_aor) << endl; config << FLD_ALLOW_XFER_CONSULT_INPROG << '='; config << bool2yesno(allow_transfer_consultation_inprog) << endl; config << FLD_SEND_P_PREFERRED_ID << '='; config << bool2yesno(send_p_preferred_id) << endl; config << endl; // Write Transport/NAT settings config << "# Transport/NAT\n"; config << FLD_SIP_TRANSPORT << '=' << sip_transport2str(sip_transport) << endl; config << FLD_SIP_TRANSPORT_UDP_THRESHOLD << '=' << sip_transport_udp_threshold << endl; if (use_nat_public_ip) { config << FLD_NAT_PUBLIC_IP << '=' << nat_public_ip << endl; } else { config << FLD_NAT_PUBLIC_IP << '=' << endl; } if (use_stun) { config << FLD_STUN_SERVER << '=' << stun_server.encode_noscheme() << endl; } else { config << FLD_STUN_SERVER << '=' << endl; } config << FLD_PERSISTENT_TCP << '=' << bool2yesno(persistent_tcp) << endl; config << FLD_ENABLE_NAT_KEEPALIVE << '=' << bool2yesno(enable_nat_keepalive) << endl; config << endl; // Write TIMER settings config << "# TIMERS\n"; config << FLD_TIMER_NOANSWER << '=' << timer_noanswer << endl; config << FLD_TIMER_NAT_KEEPALIVE << '=' << timer_nat_keepalive << endl; config << FLD_TIMER_TCP_PING << '=' << timer_tcp_ping << endl; config << endl; // Write ADDRESS FORMAT settings config << "# ADDRESS FORMAT\n"; config << FLD_DISPLAY_USERONLY_PHONE << '='; config << bool2yesno(display_useronly_phone) << endl; config << FLD_NUMERICAL_USER_IS_PHONE << '='; config << bool2yesno(numerical_user_is_phone) << endl; config << FLD_REMOVE_SPECIAL_PHONE_SYM << '='; config << bool2yesno(remove_special_phone_symbols) << endl; config << FLD_SPECIAL_PHONE_SYMBOLS << '=' << special_phone_symbols << endl; config << FLD_USE_TEL_URI_FOR_PHONE << '=' << bool2yesno(use_tel_uri_for_phone) << endl; config << endl; // Write RING TONE settings config << "# RING TONES\n"; config << FLD_USER_RINGTONE_FILE << '=' << ringtone_file << endl; config << FLD_USER_RINGBACK_FILE << '=' << ringback_file << endl; config << endl; // Write script settings config << "# SCRIPTS\n"; config << FLD_SCRIPT_INCOMING_CALL << '=' << script_incoming_call << endl; config << FLD_SCRIPT_IN_CALL_ANSWERED << '=' << script_in_call_answered << endl; config << FLD_SCRIPT_IN_CALL_FAILED << '=' << script_in_call_failed << endl; config << FLD_SCRIPT_OUTGOING_CALL << '=' << script_outgoing_call << endl; config << FLD_SCRIPT_OUT_CALL_ANSWERED << '=' << script_out_call_answered << endl; config << FLD_SCRIPT_OUT_CALL_FAILED << '=' << script_out_call_failed << endl; config << FLD_SCRIPT_LOCAL_RELEASE << '=' << script_local_release << endl; config << FLD_SCRIPT_REMOTE_RELEASE << '=' << script_remote_release << endl; config << endl; // Write number conversion rules config << "# NUMBER CONVERSION\n"; for (list::iterator i = number_conversions.begin(); i != number_conversions.end(); i++) { config << FLD_NUMBER_CONVERSION << '='; config << escape(i->re, ','); config << ','; config << escape(i->fmt, ','); config << endl; } config << endl; // Write security settings config << "# SECURITY\n"; config << FLD_ZRTP_ENABLED << '=' << bool2yesno(zrtp_enabled) << endl; config << FLD_ZRTP_GOCLEAR_WARNING << '=' << bool2yesno(zrtp_goclear_warning) << endl; config << FLD_ZRTP_SDP << '=' << bool2yesno(zrtp_sdp) << endl; config << FLD_ZRTP_SEND_IF_SUPPORTED << '=' << bool2yesno(zrtp_send_if_supported) << endl; config << endl; // Write MWI settings config << "# MWI\n"; config << FLD_MWI_SOLLICITED << '=' << bool2yesno(mwi_sollicited) << endl; config << FLD_MWI_USER << '=' << mwi_user << endl; if (mwi_server.is_valid()) { config << FLD_MWI_SERVER << '=' << mwi_server.encode_noscheme() << endl; } else { config << FLD_MWI_SERVER << '=' << endl; } config << FLD_MWI_VIA_PROXY << '=' << bool2yesno(mwi_via_proxy) << endl; config << FLD_MWI_SUBSCRIPTION_TIME << '=' << mwi_subscription_time << endl; config << FLD_MWI_VM_ADDRESS << '=' << mwi_vm_address << endl; config << endl; config << "# INSTANT MESSAGE\n"; config << FLD_IM_MAX_SESSIONS << '=' << im_max_sessions << endl; config << FLD_IM_SEND_ISCOMPOSING << '=' << bool2yesno(im_send_iscomposing) << endl; config << endl; // Write presence settings config << "# PRESENCE\n"; config << FLD_PRES_SUBSCRIPTION_TIME << '=' << pres_subscription_time << endl; config << FLD_PRES_PUBLICATION_TIME << '=' << pres_publication_time << endl; config << FLD_PRES_PUBLISH_STARTUP << '=' << bool2yesno(pres_publish_startup) << endl; // Check if writing succeeded if (!config.good()) { // Restore backup config.close(); rename(f_backup.c_str(), f.c_str()); error_msg = "File system error while writing file "; error_msg += f; log_file->write_report(error_msg, "t_user::write_config", LOG_NORMAL, LOG_CRITICAL); mtx_user.unlock(); return false; } // Set parser options t_parser::check_max_forwards = check_max_forwards; t_parser::compact_headers = compact_headers; t_parser::multi_values_as_list = encode_multi_values_as_list; mtx_user.unlock(); return true; } string t_user::get_filename(void) const { string result; mtx_user.lock(); result = config_filename; mtx_user.unlock(); return result; } bool t_user::set_config(string filename) { t_mutex_guard guard(mtx_user); struct stat stat_buf; config_filename = filename; string fullpath = expand_filename(filename); return (stat(fullpath.c_str(), &stat_buf) != 0); } string t_user::get_profile_name(void) const { string result; mtx_user.lock(); string::size_type pos_ext = config_filename.find(USER_FILE_EXT); if (pos_ext == string::npos) { result = config_filename; } else { result = config_filename.substr(0, pos_ext); } mtx_user.unlock(); return result; } string t_user::get_contact_name(void) const { mtx_user.lock(); string s = name; // Some broken proxies expect the contact name to be the same // as the SIP user name. if (!use_domain_in_contact) { mtx_user.unlock(); return s; } // Create a unique contact name from the user name and domain: // // username_domain, where all dots in domain are replace // // This way it is possible to activate 2 profiles that have the // same username, but different domains, e.g. // // michel@domainA // michel@domainB s += '_'; // Cut of port and/or uri-parameters if present in domain string::size_type i = domain.find_first_of(":;"); if (i != string::npos) { // Some broken SIP proxies think that their own address appears // in the contact header when they see the domain in the user part. // By replacing the dots with underscores Twinkle interoperates // with those proxies (yuck). s += replace_char(domain.substr(0, i), '.', '_'); } else { s += replace_char(domain, '.', '_'); } mtx_user.unlock(); return s; } string t_user::get_display_uri(void) const { mtx_user.lock(); string s; s = display; if (!s.empty()) s += ' '; s += '<'; s += USER_SCHEME; s += ':'; s += name; s += '@'; s += domain; s += '>'; mtx_user.unlock(); return s; } bool t_user::check_required_ext(t_request *r, list &unsupported) const { bool all_supported = true; mtx_user.lock(); unsupported.clear(); if (!r->hdr_require.is_populated()) { mtx_user.unlock(); return true; } for (list::iterator i = r->hdr_require.features.begin(); i != r->hdr_require.features.end(); i++) { if (*i == EXT_100REL) { if (ext_100rel != EXT_DISABLED) continue; } else if (*i == EXT_REPLACES) { if (ext_replaces) continue; } else if (*i == EXT_NOREFERSUB) { continue; } // Extension is not supported unsupported.push_back(*i); all_supported = false; } mtx_user.unlock(); return all_supported; } string t_user::create_user_contact(bool anonymous, const string &auto_ip) { string s; mtx_user.lock(); s = USER_SCHEME; s += ':'; if (!anonymous) { s += t_url::escape_user_value(get_contact_name()); s += '@'; } s += USER_HOST(this, auto_ip); if (PUBLIC_SIP_PORT(this) != get_default_port(USER_SCHEME)) { s += ':'; s += int2str(PUBLIC_SIP_PORT(this)); } if (phone->use_stun(this)) { // The port discovered via STUN can only be used for UDP. s += ";transport=udp"; } else { // Add transport parameter if a single transport is provisioned only. switch (sip_transport) { case SIP_TRANS_UDP: s += ";transport=udp"; break; case SIP_TRANS_TCP: s += ";transport=tcp"; break; default: break; } } if (!anonymous && numerical_user_is_phone && looks_like_phone(name, special_phone_symbols)) { // RFC 3261 19.1.1 // If the URI contains a telephone number it SHOULD contain // the user=phone parameter. s += ";user=phone"; } mtx_user.unlock(); return s; } string t_user::create_user_uri(bool anonymous) { if (anonymous) return ANONYMOUS_URI; string s; mtx_user.lock(); s = USER_SCHEME; s += ':'; s += t_url::escape_user_value(name); s += '@'; s += domain; if (numerical_user_is_phone && looks_like_phone(name, special_phone_symbols)) { // RFC 3261 19.1.1 // If the URI contains a telephone number it SHOULD contain // the user=phone parameter. s += ";user=phone"; } mtx_user.unlock(); return s; } string t_user::convert_number(const string &number, const list &l) const { for (list::const_iterator i = l.begin(); i != l.end(); i++) { std::smatch m; try { if (std::regex_match(number, m, std::regex(i->re))) { string result; m.format(std::back_inserter(result), i->fmt); log_file->write_header("t_user::convert_number", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Apply conversion: "); log_file->write_raw(i->str()); log_file->write_endl(); log_file->write_raw(number); log_file->write_raw(" converted to "); log_file->write_raw(result); log_file->write_endl(); log_file->write_footer(); return result; } } catch (std::runtime_error) { log_file->write_header("t_user::convert_number", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Number conversion rule too complex:\n"); log_file->write_raw("Number: "); log_file->write_raw(number); log_file->write_endl(); log_file->write_raw(i->str()); log_file->write_endl(); log_file->write_footer(); return number; } } // No match found return number; } string t_user::convert_number(const string &number) const { return convert_number(number, number_conversions); } t_url t_user::get_mwi_uri(void) const { t_url u(mwi_server); u.set_user(mwi_user); return u; } bool t_user::is_diamondcard_account(void) const { // A profile is a Diamondcard account if the end configured domain // is equal to the DIAMONDCARD_DOMAIN size_t domain_len = strlen(DIAMONDCARD_DOMAIN); if (domain.size() < domain_len) return false; size_t pos = domain.size() - domain_len; return (domain.substr(pos) == DIAMONDCARD_DOMAIN); } twinkle-1.10.1/src/user.h000066400000000000000000000677401277565361200152210ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ // NOTE: // When adding attributes to t_user, make sure to add them to the // copy constructor too! #ifndef _H_USER #define _H_USER #include #include #include "protocol.h" #include "sys_settings.h" #include "audio/audio_codecs.h" #include "sockets/url.h" #include "threads/mutex.h" #include // Forward declaration class t_request; // Default config file name #define USER_CONFIG_FILE "twinkle.cfg" #define USER_FILE_EXT ".cfg" #define USER_DIR DIR_USER #define USER_SCHEME "sip" #define PUBLIC_SIP_PORT(u) phone->get_public_port_sip(u) #define USER_HOST(u,local_ip) phone->get_ip_sip(u,(local_ip)) #define LOCAL_IP user_host #define LOCAL_HOSTNAME local_hostname #define SPECIAL_PHONE_SYMBOLS "-()/." using namespace std; enum t_hold_variant { HOLD_RFC2543, // set IP = 0.0.0.0 in c-line HOLD_RFC3264 // use direction attribute to put call on-hold }; /** SIP transport mode */ enum t_sip_transport { SIP_TRANS_UDP, /**< SIP over UDP */ SIP_TRANS_TCP, /**< SIP over TCP */ SIP_TRANS_AUTO /**< UDP for small messages, TCP for large messages */ }; enum t_ext_support { EXT_INVALID, EXT_DISABLED, EXT_SUPPORTED, EXT_PREFERRED, EXT_REQUIRED }; enum t_bit_rate_type { BIT_RATE_INVALID, BIT_RATE_CBR, // Constant BIT_RATE_VBR, // Variable BIT_RATE_ABR // Average }; enum t_dtmf_transport { DTMF_INBAND, DTMF_RFC2833, DTMF_AUTO, DTMF_INFO }; enum t_g726_packing { G726_PACK_RFC3551, G726_PACK_AAL2 }; struct t_number_conversion { string re; string fmt; string str(void) const { return re + " --> " + fmt; } }; class t_user { private: string config_filename; // Mutex for exclusive access to the user profile mutable t_recursive_mutex mtx_user; /** @name USER */ //@{ // SIP user /** User name (public user identity). */ string name; /** Domain of the user. */ string domain; /** Display name. */ string display; /** * The organization will be put in an initial INVITE and in a * 200 OK on an INVITE. */ string organization; // SIP authentication /** Authentication realm. An empty realm matches with all realms. */ string auth_realm; /** Authentication name (private user identity). */ string auth_name; /** Authentication password (aka_k for akav1-md5 authentication) */ string auth_pass; /** Operator variant key for akav1-md5 authentication. */ uint8 auth_aka_op[AKA_OPLEN]; /** Authentication management field for akav1-md5 authentication. */ uint8 auth_aka_amf[AKA_AMFLEN]; //@} // SIP SERVER // Send all non-REGISTER requests to the outbound proxy bool use_outbound_proxy; t_url outbound_proxy; // By default only out-of-dialog requests (including the ones the // establish a dialog) are sent to the outbound proxy. // In-dialog requests go to the address established via the contact // header. // By setting this parameter to true, in-dialog requests go to the // outbound proxy as well. bool all_requests_to_proxy; // Only send the request to the proxy, if the destination cannot // be resolved to an IP address, adhearing to the previous setting // though. I.e. use_outbound_proxy must be true. And an in-dialog // request will only be sent to the proxy if all_requests_to_proxy // is true. bool non_resolvable_to_proxy; // Send REGISTER to registrar bool use_registrar; t_url registrar; // Registration time requested by the client. If set to zero, then // no specific time is requested. The registrar will set a time. unsigned long registration_time; // Automatically register at startup of the client. bool register_at_startup; // q-value for registration bool reg_add_qvalue; float reg_qvalue; // AUDIO list codecs; // in order of preference unsigned short ptime; // ptime (ms) for G.711/G.726 // For outgoing calls, obey the preference from the far-end (SDP answer), // i.e. pick the first codec from the SDP answer that we support. bool out_obey_far_end_codec_pref; // For incoming calls, obey the preference from the far-end (SDP offer), // i.e. pick the first codec from the SDP offer that we support. bool in_obey_far_end_codec_pref; // RTP dynamic payload types for speex unsigned short speex_nb_payload_type; unsigned short speex_wb_payload_type; unsigned short speex_uwb_payload_type; // Speex preprocessing options bool speex_dsp_vad; // voice activity reduction bool speex_dsp_agc; // automatic gain control bool speex_dsp_aec; // acoustic echo cancellation bool speex_dsp_nrd; // noise reduction unsigned short speex_dsp_agc_level; // gain level of AGC (1-100[%]) // Speex coding options t_bit_rate_type speex_bit_rate_type; int speex_abr_nb; int speex_abr_wb; bool speex_dtx; bool speex_penh; unsigned short speex_complexity; unsigned short speex_quality; // quality measure (worst 0-10 best) // RTP dynamic payload types for iLBC unsigned short ilbc_payload_type; // iLBC options unsigned short ilbc_mode; // 20 or 30 ms frame size // RTP dynamic payload types for G.726 unsigned short g726_16_payload_type; unsigned short g726_24_payload_type; unsigned short g726_32_payload_type; unsigned short g726_40_payload_type; // Bit packing order for G,726 t_g726_packing g726_packing; // Transport mode for DTMF t_dtmf_transport dtmf_transport; // RTP dynamic payload type for out-of-band DTMF. unsigned short dtmf_payload_type; // DTMF duration and pause between 2 tones. During the pause the last // DTMF event will be repeated so the far end can detect the end of // the event in case of packet loss. unsigned short dtmf_duration; // ms unsigned short dtmf_pause; // ms // Volume of the tone in -dBm unsigned short dtmf_volume; /** @name SIP PROTOCOL */ //@{ // SIP protocol options // hold variants: rfc2543, rfc3264 // rfc2543 - set IP address to 0.0.0.0 // rfc3264 - use direction attribute (sendrecv, sendonly, ...) t_hold_variant hold_variant; // Indicate if the mandatory Max-Forwards header should be present. // If true and the header is missing, then the request will fail. bool check_max_forwards; // RFC 3261 10.3 states that a registrar must include a contact // header in a 200 OK on a REGISTER. This contact should match the // contact that a UA puts in the REGISTER. Unfortunately many // registrars do not include the contact header or put a wrong // IP address in the host-part due to NAT. // This settings allows for a missing/non-matching contact header. // In that case Twinkle assumes that it is registered for the // requested interval. bool allow_missing_contact_reg; // Indicate the place of the requested registration time in a REGISTER. // true - expires parameter in contact header // false - Expires header bool registration_time_in_contact; // Indicate if compact header names should be used in outgoing messages. bool compact_headers; // Indicate if headers containing multiple values should be encoded // as a comma separated list or as multiple headers. bool encode_multi_values_as_list; // Indicate if a unique contact name should be created by using // the domain name: username_domain // If false then the SIP user name is used as contact name bool use_domain_in_contact; // Allow SDP to change in different INVITE responses. // According to RFC 3261 13.2.1, if SDP is received in a 1XX response, // then SDP received in subsequent responses should be ignored. // Some SIP proxies do send different SDP in 1XX and 200 though. // E.g. first SDP is to play ring tone, second SDP is to create // an end-to-end media path. bool allow_sdp_change; // Redirections // Allow redirection of a request when a 3XX is received. bool allow_redirection; // Ask user for permission to redirect a request when a 3XX is received. bool ask_user_to_redirect; // Maximum number of locations to be tried when a request is redirected. unsigned short max_redirections; // SIP extensions // 100rel extension (PRACK, RFC 3262) // Possible values: // - disabled 100rel extension is disabled // - supported 100rel is supported (it is added in the supported header of // an outgoing INVITE). A far-end can now require a PRACK on a // 1xx response. // - required 100rel is required (it is put in the require header of an // outgoing INVITE). // If an incoming INVITE indicates that it supports 100rel, then // Twinkle will require a PRACK when sending a 1xx response. // - preferred Similar to required, but if a call fails because the far-end // indicates it does not support 100rel (420 response) then the // call will be re-attempted without the 100rel requirement. t_ext_support ext_100rel; // Replaces (RFC 3891) bool ext_replaces; //@} /** @name REFER options */ //@{ /** Hold the current call when an incoming REFER is accepted. */ bool referee_hold; /** Hold the current call before sending a REFER. */ bool referrer_hold; /** Allow an incoming refer */ bool allow_refer; /** Ask user for permission when a REFER is received. */ bool ask_user_to_refer; /** Referrer automatically refreshes subscription before expiry. */ bool auto_refresh_refer_sub; /** * An attended transfer should use the contact-URI of the transfer target. * This contact-URI is not always globally routable however. As an * alternative the AoR (address of record) can be used. Disadvantage is * that the AoR may route to multiple phones in case of forking, whereas * the contact-URI routes to a particular phone. */ bool attended_refer_to_aor; /** * Allow to transfer a call while the consultation call is still * in progress. */ bool allow_transfer_consultation_inprog; //@} /** @name Privacy options */ //@{ // Send P-Preferred-Identity header in initial INVITE when hiding // user identity. bool send_p_preferred_id; //@} /** @name Transport */ //@{ /** SIP transport protocol */ t_sip_transport sip_transport; /** * Threshold to decide which transport to use in auto transport mode. * A message with a size up to this threshold is sent via UDP. Larger messages * are sent via TCP. */ unsigned short sip_transport_udp_threshold; //@} /** @name NAT */ //@{ /** * NAT traversal * You can set nat_public_ip to your public IP or FQDN if you are behind * a NAT. This will then be used inside the SIP messages instead of your * private IP. On your NAT you have to create static bindings for port 5060 * and ports 8000 - 8005 to the same ports on your private IP address. */ bool use_nat_public_ip; /** The public IP address of the NAT device. */ string nat_public_ip; /** NAT traversal via STUN. */ bool use_stun; /** URL of the STUN server. */ t_url stun_server; /** User persistent TCP connections. */ bool persistent_tcp; /** Enable sending of NAT keepalive packets for UDP. */ bool enable_nat_keepalive; //@} /** @name TIMERS */ //@{ /** * Noanswer timer is started when an initial INVITE is received. If * the user does not respond within the timer, then the call will be * released with a 480 Temporarily Unavailable response. */ unsigned short timer_noanswer; // seconds /** Duration of NAT keepalive timer (s) */ unsigned short timer_nat_keepalive; /** Duration of TCP ping timer (s) */ unsigned short timer_tcp_ping; //@} /** @name ADDRESS FORMAT */ //@{ /** * Telephone numbers * Display only the user-part of a URI if it is a telephone number * I.e. the user=phone parameter is present, or the user indicated * that the format of the user-part is a telephone number. * If the URI is a tel-URI then display the telephone number. */ bool display_useronly_phone; /** * Consider user-parts that consist of 0-9,+,-,*,# as a telephone * number. I.e. in outgoing messages the user=phone parameter will * be added to the URI. For incoming messages the URI will be considered * to be a telephone number regardless of the presence of the * user=phone parameter. */ bool numerical_user_is_phone; /** Remove special symbols from numerical dial strings */ bool remove_special_phone_symbols; /** Special symbols that must be removed from telephone numbers */ string special_phone_symbols; /** * If the user enters a telephone number as address, then complete it * to a tel-URI instead of a sip-URI. */ bool use_tel_uri_for_phone; /** Number conversion */ list number_conversions; //@} // RING TONES string ringtone_file; string ringback_file; // SCRIPTS // Script to be called on incoming call string script_incoming_call; string script_in_call_answered; string script_in_call_failed; string script_outgoing_call; string script_out_call_answered; string script_out_call_failed; string script_local_release; string script_remote_release; // SECURITY // zrtp setting bool zrtp_enabled; // Popup warning when far-end sends goclear command bool zrtp_goclear_warning; // Send a=zrtp in SDP bool zrtp_sdp; // Only negotiate zrtp if far-end signalled support for zrtp bool zrtp_send_if_supported; // MWI // Indicate if MWI is sollicited or unsollicited. // RFC 3842 specifies that MWI must be sollicited (SUBSCRIBE). // Asterisk however only supported non-standard unsollicited MWI. bool mwi_sollicited; // User name for subscribing to the mailbox string mwi_user; // The mailbox server to which the SUBSCRIBE must be sent t_url mwi_server; // Send the SUBSCRIBE via the proxy to the mailbox server bool mwi_via_proxy; // Requested MWI subscription duration unsigned long mwi_subscription_time; // The voice mail address to call to access messages string mwi_vm_address; /** @name INSTANT MESSAGE */ //@{ /** Maximum number of simultaneous IM sessions. */ unsigned short im_max_sessions; /** Flag to indicate that IM is-composing indications (RFC 3994) should be sent. */ bool im_send_iscomposing; //@} /** @name PRESENCE */ //@{ /** Requested presence subscription duration in seconds. */ unsigned long pres_subscription_time; /** Requested presence publication duration in seconds. */ unsigned long pres_publication_time; /** Publish online presence state at startup */ bool pres_publish_startup; //@} t_ext_support str2ext_support(const string &s) const; string ext_support2str(t_ext_support e) const; t_bit_rate_type str2bit_rate_type(const string &s) const; string bit_rate_type2str(t_bit_rate_type b) const; t_dtmf_transport str2dtmf_transport(const string &s) const; string dtmf_transport2str(t_dtmf_transport d) const; t_g726_packing str2g726_packing(const string &s) const; string g726_packing2str(t_g726_packing packing) const; t_sip_transport str2sip_transport(const string &s) const; string sip_transport2str(t_sip_transport transport) const; // Parse a number conversion rule // If the rule can be parsed, then c contains the conversion rule and // true is returned. Otherwise false is returned. bool parse_num_conversion(const string &value, t_number_conversion &c); // Set a server URL. // Returns false, if the passed value is not a valid URL. bool set_server_value(t_url &server, const string &scheme, const string &value); public: t_user(); t_user(const t_user &u); t_user *copy(void) const; /** @name Getters */ //@{ string get_name(void) const; string get_domain(void) const; string get_display(bool anonymous) const; string get_organization(void) const; string get_auth_realm(void) const; string get_auth_name(void) const; string get_auth_pass(void) const; void get_auth_aka_op(uint8 *aka_op) const; void get_auth_aka_amf(uint8 *aka_amf) const; bool get_use_outbound_proxy(void) const; t_url get_outbound_proxy(void) const; bool get_all_requests_to_proxy(void) const; bool get_non_resolvable_to_proxy(void) const; bool get_use_registrar(void) const; t_url get_registrar(void) const; unsigned long get_registration_time(void) const; bool get_register_at_startup(void) const; bool get_reg_add_qvalue(void) const; float get_reg_qvalue(void) const; list get_codecs(void) const; unsigned short get_ptime(void) const; bool get_out_obey_far_end_codec_pref(void) const; bool get_in_obey_far_end_codec_pref(void) const; unsigned short get_speex_nb_payload_type(void) const; unsigned short get_speex_wb_payload_type(void) const; unsigned short get_speex_uwb_payload_type(void) const; t_bit_rate_type get_speex_bit_rate_type(void) const; int get_speex_abr_nb(void) const; int get_speex_abr_wb(void) const; bool get_speex_dtx(void) const; bool get_speex_penh(void) const; unsigned short get_speex_quality(void) const; unsigned short get_speex_complexity(void) const; bool get_speex_dsp_vad(void) const; bool get_speex_dsp_agc(void) const; bool get_speex_dsp_aec(void) const; bool get_speex_dsp_nrd(void) const; unsigned short get_speex_dsp_agc_level(void) const; unsigned short get_ilbc_payload_type(void) const; unsigned short get_ilbc_mode(void) const; unsigned short get_g726_16_payload_type(void) const; unsigned short get_g726_24_payload_type(void) const; unsigned short get_g726_32_payload_type(void) const; unsigned short get_g726_40_payload_type(void) const; t_g726_packing get_g726_packing(void) const; t_dtmf_transport get_dtmf_transport(void) const; unsigned short get_dtmf_payload_type(void) const; unsigned short get_dtmf_duration(void) const; unsigned short get_dtmf_pause(void) const; unsigned short get_dtmf_volume(void) const; t_hold_variant get_hold_variant(void) const; bool get_check_max_forwards(void) const; bool get_allow_missing_contact_reg(void) const; bool get_registration_time_in_contact(void) const; bool get_compact_headers(void) const; bool get_encode_multi_values_as_list(void) const; bool get_use_domain_in_contact(void) const; bool get_allow_sdp_change(void) const; bool get_allow_redirection(void) const; bool get_ask_user_to_redirect(void) const; unsigned short get_max_redirections(void) const; t_ext_support get_ext_100rel(void) const; bool get_ext_replaces(void) const; bool get_referee_hold(void) const; bool get_referrer_hold(void) const; bool get_allow_refer(void) const; bool get_ask_user_to_refer(void) const; bool get_auto_refresh_refer_sub(void) const; bool get_attended_refer_to_aor(void) const; bool get_allow_transfer_consultation_inprog(void) const; bool get_send_p_preferred_id(void) const; t_sip_transport get_sip_transport(void) const; unsigned short get_sip_transport_udp_threshold(void) const; bool get_use_nat_public_ip(void) const; string get_nat_public_ip(void) const; bool get_use_stun(void) const; t_url get_stun_server(void) const; bool get_persistent_tcp(void) const; bool get_enable_nat_keepalive(void) const; unsigned short get_timer_noanswer(void) const; unsigned short get_timer_nat_keepalive(void) const; unsigned short get_timer_tcp_ping(void) const; bool get_display_useronly_phone(void) const; bool get_numerical_user_is_phone(void) const; bool get_remove_special_phone_symbols(void) const; string get_special_phone_symbols(void) const; bool get_use_tel_uri_for_phone(void) const; string get_ringtone_file(void) const; string get_ringback_file(void) const; string get_script_incoming_call(void) const; string get_script_in_call_answered(void) const; string get_script_in_call_failed(void) const; string get_script_outgoing_call(void) const; string get_script_out_call_answered(void) const; string get_script_out_call_failed(void) const; string get_script_local_release(void) const; string get_script_remote_release(void) const; list get_number_conversions(void) const; bool get_zrtp_enabled(void) const; bool get_zrtp_goclear_warning(void) const; bool get_zrtp_sdp(void) const; bool get_zrtp_send_if_supported(void) const; bool get_mwi_sollicited(void) const; string get_mwi_user(void) const; t_url get_mwi_server(void) const; bool get_mwi_via_proxy(void) const; unsigned long get_mwi_subscription_time(void) const; string get_mwi_vm_address(void) const; unsigned short get_im_max_sessions(void) const; bool get_im_send_iscomposing(void) const; unsigned long get_pres_subscription_time(void) const; unsigned long get_pres_publication_time(void) const; bool get_pres_publish_startup(void) const; //@} /** @name Setters */ //@{ void set_name(const string &_name); void set_domain(const string &_domain); void set_display(const string &_display); void set_organization(const string &_organization); void set_auth_realm(const string &realm); void set_auth_name(const string &name); void set_auth_pass(const string &pass); void set_auth_aka_op(const uint8 *aka_op); void set_auth_aka_amf(const uint8 *aka_amf); void set_use_outbound_proxy(bool b); void set_outbound_proxy(const t_url &url); void set_all_requests_to_proxy(bool b); void set_non_resolvable_to_proxy(bool b); void set_use_registrar(bool b); void set_registrar(const t_url &url); void set_registration_time(const unsigned long time); void set_register_at_startup(bool b); void set_reg_add_qvalue(bool b); void set_reg_qvalue(float q); void set_codecs(const list &_codecs); void set_ptime(unsigned short _ptime); void set_out_obey_far_end_codec_pref(bool b); void set_in_obey_far_end_codec_pref(bool b); void set_speex_nb_payload_type(unsigned short payload_type); void set_speex_wb_payload_type(unsigned short payload_type); void set_speex_uwb_payload_type(unsigned short payload_type); void set_speex_bit_rate_type(t_bit_rate_type bit_rate_type); void set_speex_abr_nb(int abr); void set_speex_abr_wb(int abr); void set_speex_dtx(bool b); void set_speex_penh(bool b); void set_speex_quality(unsigned short quality); void set_speex_complexity(unsigned short complexity); void set_speex_dsp_vad(bool b); void set_speex_dsp_agc(bool b); void set_speex_dsp_aec(bool b); void set_speex_dsp_nrd(bool b); void set_speex_dsp_agc_level(unsigned short level); void set_ilbc_payload_type(unsigned short payload_type); void set_g726_16_payload_type(unsigned short payload_type); void set_g726_24_payload_type(unsigned short payload_type); void set_g726_32_payload_type(unsigned short payload_type); void set_g726_40_payload_type(unsigned short payload_type); void set_g726_packing(t_g726_packing packing); void set_ilbc_mode(unsigned short mode); void set_dtmf_transport(t_dtmf_transport _dtmf_transport); void set_dtmf_payload_type(unsigned short payload_type); void set_dtmf_duration(unsigned short duration); void set_dtmf_pause(unsigned short pause); void set_dtmf_volume(unsigned short volume); void set_hold_variant(t_hold_variant _hold_variant); void set_check_max_forwards(bool b); void set_allow_missing_contact_reg(bool b); void set_registration_time_in_contact(bool b); void set_compact_headers(bool b); void set_encode_multi_values_as_list(bool b); void set_use_domain_in_contact(bool b); void set_allow_sdp_change(bool b); void set_allow_redirection(bool b); void set_ask_user_to_redirect(bool b); void set_max_redirections(unsigned short _max_redirections); void set_ext_100rel(t_ext_support ext_support); void set_ext_replaces(bool b); void set_referee_hold(bool b); void set_referrer_hold(bool b); void set_allow_refer(bool b); void set_ask_user_to_refer(bool b); void set_auto_refresh_refer_sub(bool b); void set_attended_refer_to_aor(bool b); void set_allow_transfer_consultation_inprog(bool b); void set_send_p_preferred_id(bool b); void set_sip_transport(t_sip_transport transport); void set_sip_transport_udp_threshold(unsigned short threshold); void set_use_nat_public_ip(bool b); void set_nat_public_ip(const string &public_ip); void set_use_stun(bool b); void set_stun_server(const t_url &url); void set_persistent_tcp(bool b); void set_enable_nat_keepalive(bool b); void set_timer_noanswer(unsigned short timer); void set_timer_nat_keepalive(unsigned short timer); void set_timer_tcp_ping(unsigned short timer); void set_display_useronly_phone(bool b); void set_numerical_user_is_phone(bool b); void set_remove_special_phone_symbols(bool b); void set_special_phone_symbols(const string &symbols); void set_use_tel_uri_for_phone(bool b); void set_ringtone_file(const string &file); void set_ringback_file(const string &file); void set_script_incoming_call(const string &script); void set_script_in_call_answered(const string &script); void set_script_in_call_failed(const string &script); void set_script_outgoing_call(const string &script); void set_script_out_call_answered(const string &script); void set_script_out_call_failed(const string &script); void set_script_local_release(const string &script); void set_script_remote_release(const string &script); void set_number_conversions(const list &l); void set_zrtp_enabled(bool b); void set_zrtp_goclear_warning(bool b); void set_zrtp_sdp(bool b); void set_zrtp_send_if_supported(bool b); void set_mwi_sollicited(bool b); void set_mwi_user(const string &user); void set_mwi_server(const t_url &url); void set_mwi_via_proxy(bool b); void set_mwi_subscription_time(unsigned long t); void set_mwi_vm_address(const string &address); void set_im_max_sessions(unsigned short max_sessions); void set_im_send_iscomposing(bool b); void set_pres_subscription_time(unsigned long t); void set_pres_publication_time(unsigned long t); void set_pres_publish_startup(bool b); //@} // Read and parse a config file into the user object. // Returns false if it fails. error_msg is an error message that can // be given to the user. bool read_config(const string &filename, string &error_msg); /** * Write the settings into a config file. * @param filename [in] Name of the file to write. * @param error_msg [out] Human readable error message when writing fails. * @return Returns true of writing succeeded, otherwise false. */ bool write_config(const string &filename, string &error_msg); /** Get the file name for this user profile */ string get_filename(void) const; /** * Set a config file name. * @return True if file name did not yet exist. * @return False if file name already exists. */ bool set_config(string _filename); // Get the name of the profile (filename without extension) string get_profile_name(void) const; // Expand file name to a fully qualified file name string expand_filename(const string &filename); // The contact name is created from the name and domain values. // Just the name value is not unique when multiple user profiles are // activated. string get_contact_name(void) const; // Returns "display " string get_display_uri(void) const; // Check if all required extensions are supported bool check_required_ext(t_request *r, list &unsupported) const; /** * Create contact URI. * @param anonymous [in] Indicates if an anonymous contact should be created. * @param auto_ip [in] Automatically determined local IP address that should be * used if not IP address has been determined through other means. * @return String representation of the contact URI. */ string create_user_contact(bool anonymous, const string &auto_ip); // Create user uri string create_user_uri(bool anonymous); // Convert a number by applying the number conversions. string convert_number(const string &number, const list &l) const; string convert_number(const string &number) const; // Get URI for sending a SUBSCRIBE for MWI t_url get_mwi_uri(void) const; /** Is this a user profile for a Diamondcard account? */ bool is_diamondcard_account(void) const; }; #endif twinkle-1.10.1/src/userintf.cpp000066400000000000000000002500201277565361200164160ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include "address_book.h" #include "events.h" #include "line.h" #include "log.h" #include "sys_settings.h" #include "translator.h" #include "userintf.h" #include "util.h" #include "user.h" #include "audio/rtp_telephone_event.h" #include "parser/parse_ctrl.h" #include "sockets/interfaces.h" #include "audits/memman.h" #include "utils/file_utils.h" #include "utils/mime_database.h" #define CLI_PROMPT "Twinkle> " #define CLI_MAX_HISTORY_LENGTH 1000 extern string user_host; extern t_event_queue *evq_trans_layer; using namespace utils; //////////////////////// // GNU Readline helpers //////////////////////// char ** tw_completion (const char *text, int start, int end); char * tw_command_generator (const char *text, int state); char ** tw_completion (const char *text, int start, int end) { char **matches; matches = (char **)NULL; if (start == 0) matches = rl_completion_matches (text, tw_command_generator); return (matches); } char * tw_command_generator (const char *text, int state) { static int len; static list::const_iterator i; if (!state){ len = strlen(text); i = ui->get_all_commands().begin(); } for (; i != ui->get_all_commands().end(); i++){ const char * s = i->c_str(); //cout << s << endl; if ( s && strncmp(s, text, len) == 0 ){ i++; return strdup(s); } } /* If no names matched, then return NULL. */ return ((char *)NULL); } char *tw_readline(const char *prompt) { static char *line = NULL; if (!line) { free(line); line = NULL; } line = readline(prompt); if (line && *line) { add_history(line); } return line; } ///////////////////////////// // Private ///////////////////////////// string t_userintf::expand_destination(t_user *user_config, const string &dst, const string &scheme) { assert(user_config); string s = dst; // Apply number conversion rules if applicable // Add domain if it is missing from a sip-uri if (s.find('@') == string::npos) { bool is_tel_uri = (s.substr(0, 4) == "tel:"); // Strip tel-scheme if (is_tel_uri) s = s.substr(4); // Remove white space s = remove_white_space(s); // Remove special phone symbols if (user_config->get_remove_special_phone_symbols() && looks_like_phone(s, user_config->get_special_phone_symbols())) { s = remove_symbols(s, user_config->get_special_phone_symbols()); } // Convert number according to the number conversion rules s = user_config->convert_number(s); if (is_tel_uri) { // Add tel-scheme again. s = "tel:" + s; } else if (s.substr(0, 4) != "sip:" && (user_config->get_use_tel_uri_for_phone() || scheme == "tel") && user_config->get_numerical_user_is_phone() && looks_like_phone(s, user_config->get_special_phone_symbols())) { // Add tel-scheme if a telephone number must be expanded // to a tel-uri according to user profile settings. s = "tel:" + s; } else { // Add domain s += '@'; s += user_config->get_domain(); } } // Add sip-scheme if a scheme is missing if (s.substr(0, 4) != "sip:" && s.substr(0, 4) != "tel:") { s = "sip:" + s; } // RFC 3261 19.1.1 // Add user=phone for telehpone numbers in a SIP-URI // If the SIP-URI contains a telephone number it SHOULD contain // the user=phone parameter. if (user_config->get_numerical_user_is_phone() && s.substr(0, 4) == "sip:") { t_url u(s); if (u.get_user_param().empty() && u.user_looks_like_phone(user_config->get_special_phone_symbols())) { s += ";user=phone"; } } return s; } void t_userintf::expand_destination(t_user *user_config, const string &dst, string &display, string &dst_url) { display.clear(); dst_url.clear(); if (dst.empty()) { return; } // If there is a display name then the url part is between angle // brackets. if (dst[dst.size() - 1] != '>') { dst_url = expand_destination(user_config, dst); return; } // Find start of url string::size_type i = dst.rfind('<'); if (i == string::npos) { // It seems the string is invalid. return; } dst_url = expand_destination(user_config, dst.substr(i + 1, dst.size() - i - 2)); if (i > 0) { display = unquote(trim(dst.substr(0, i))); } } void t_userintf::expand_destination(t_user *user_config, const string &dst, t_display_url &display_url) { string url_str; expand_destination(user_config, dst, display_url.display, url_str); display_url.url.set_url(url_str); } void t_userintf::expand_destination(t_user *user_config, const string &dst, t_display_url &display_url, string &subject, string &dst_no_headers) { string headers; dst_no_headers = dst; t_url u(dst); // Split headers from URI if (u.is_valid()) { // destination is a valid URI. Strip off the headers if any headers = u.get_headers(); // Cut off headers // Note that a separator (?) will be in front of the // headers string if (!headers.empty()) { string::size_type i = dst.find(headers); if (i != string::npos) { dst_no_headers = dst.substr(0, i - 1); } } expand_destination(user_config, dst_no_headers, display_url); } else { // destination may be a short URI. // Split at a '?' to find any headers. // NOTE: this is not fool proof. A user name may contain a '?' vector l = split_on_first(dst, '?'); dst_no_headers = l[0]; expand_destination(user_config, dst_no_headers, display_url); if (display_url.is_valid() && l.size() == 2) { headers = l[1]; } } // Parse headers to find subject header subject.clear(); if (!headers.empty()) { try { list parse_errors; t_sip_message *m = t_parser::parse_headers(headers, parse_errors); if (m->hdr_subject.is_populated()) { subject = m->hdr_subject.subject; } MEMMAN_DELETE(m); delete m; } catch (int) { // ignore invalid headers } } } bool t_userintf::parse_args(const list command_list, list &al) { t_command_arg arg; bool parsed_flag = false; al.clear(); arg.flag = 0; arg.value = ""; for (list::const_iterator i = command_list.begin(); i != command_list.end(); i++) { if (i == command_list.begin()) continue; const string &s = *i; if (s[0] == '-') { if (s.size() == 1) return false; if (parsed_flag) al.push_back(arg); arg.flag = s[1]; if (s.size() > 2) { arg.value = unquote(s.substr(2)); al.push_back(arg); arg.flag = 0; arg.value = ""; parsed_flag = false; } else { arg.value = ""; parsed_flag = true; } } else { if (parsed_flag) { arg.value = unquote(s); } else { arg.flag = 0; arg.value = unquote(s); } al.push_back(arg); parsed_flag = false; arg.flag = 0; arg.value = ""; } } // Last parsed argument was a flag only if (parsed_flag) al.push_back(arg); return true; } bool t_userintf::exec_invite(const list command_list, bool immediate) { list al; string display; string subject; string destination; bool hide_user = false; if (!parse_args(command_list, al)) { exec_command("help call"); return false; } for (list::iterator i = al.begin(); i != al.end(); i++) { switch (i->flag) { case 'd': display = i->value; break; case 's': subject = i->value; break; case 'h': hide_user = true; break; case 0: destination = i->value; break; default: exec_command("help call"); return false; break; } } return do_invite(destination, display, subject, immediate, hide_user); } bool t_userintf::do_invite(const string &destination, const string &display, const string &subject, bool immediate, bool anonymous) { t_url dest_url(expand_destination(active_user, destination)); if (!dest_url.is_valid()) { exec_command("help call"); return false; } t_url vm_url(expand_destination(active_user, active_user->get_mwi_vm_address())); if (dest_url != vm_url) { // Keep call information for redial last_called_url = dest_url; last_called_display = display; last_called_subject = subject; last_called_profile = active_user->get_profile_name(); last_called_hide_user = anonymous; } phone->pub_invite(active_user, dest_url, display, subject, anonymous); return true; } bool t_userintf::exec_redial(const list command_list) { if (can_redial()) { do_redial(); return true; } return false; } void t_userintf::do_redial(void) { t_user *user_config = phone->ref_user_profile(last_called_profile); phone->pub_invite(user_config, last_called_url, last_called_display, last_called_subject, last_called_hide_user); } bool t_userintf::exec_answer(const list command_list) { do_answer(); return true; } void t_userintf::do_answer(void) { cb_stop_call_notification(phone->get_active_line()); phone->pub_answer(); } bool t_userintf::exec_answerbye(const list command_list) { do_answerbye(); return true; } void t_userintf::do_answerbye(void) { unsigned short line = phone->get_active_line(); switch (phone->get_line_substate(line)) { case LSSUB_INCOMING_PROGRESS: do_answer(); break; case LSSUB_OUTGOING_PROGRESS: case LSSUB_ESTABLISHED: do_bye(); break; default: break; } } bool t_userintf::exec_reject(const list command_list) { do_reject(); return true; } void t_userintf::do_reject(void) { cb_stop_call_notification(phone->get_active_line()); phone->pub_reject(); cout << endl; cout << "Line " << phone->get_active_line() + 1 << ": call rejected.\n"; cout << endl; } bool t_userintf::exec_redirect(const list command_list, bool immediate) { list al; list dest_list; int num_redirections = 0; bool show_status = false; bool action_present = false; bool enable = true; bool type_present = false; t_cf_type cf_type = CF_ALWAYS; if (!parse_args(command_list, al)) { exec_command("help redirect"); return false; } for (list::iterator i = al.begin(); i != al.end(); i++) { switch (i->flag) { case 's': show_status = true; break; case 't': if (i->value == "always") { cf_type = CF_ALWAYS; } else if (i->value == "busy") { cf_type = CF_BUSY; } else if (i->value == "noanswer") { cf_type = CF_NOANSWER; } else { exec_command("help redirect"); return false; } type_present = true; break; case 'a': if (i->value == "on") { enable = true; } else if (i->value == "off") { enable = false; } else { exec_command("help redirect"); return false; } action_present = true; break; case 0: dest_list.push_back(i->value); num_redirections++; break; default: exec_command("help redirect"); return false; break; } } if (type_present && enable && (num_redirections == 0 || num_redirections > 5)) { exec_command("help redirect"); return false; } if (!type_present && action_present && enable) { exec_command("help redirect"); return false; } if (!type_present && !action_present && (num_redirections == 0 || num_redirections > 5)) { exec_command("help redirect"); return false; } do_redirect(show_status, type_present, cf_type, action_present, enable, num_redirections, dest_list, immediate); return true; } void t_userintf::do_redirect(bool show_status, bool type_present, t_cf_type cf_type, bool action_present, bool enable, int num_redirections, const list &dest_strlist, bool immediate) { list dest_list; for (list::const_iterator i = dest_strlist.begin(); i != dest_strlist.end(); i++) { t_display_url du; du.url = expand_destination(active_user, *i); du.display.clear(); if (!du.is_valid()) return; dest_list.push_back(du); } if (show_status) { list cf_dest; // call forwarding destinations cout << endl; cout << "Redirect always: "; if (phone->ref_service(active_user)->get_cf_active(CF_ALWAYS, cf_dest)) { for (list::iterator i = cf_dest.begin(); i != cf_dest.end(); i++) { if (i != cf_dest.begin()) cout << ", "; cout << i->encode(); } } else { cout << "not active"; } cout << endl; cout << "Redirect busy: "; if (phone->ref_service(active_user)->get_cf_active(CF_BUSY, cf_dest)) { for (list::iterator i = cf_dest.begin(); i != cf_dest.end(); i++) { if (i != cf_dest.begin()) cout << ", "; cout << i->encode(); } } else { cout << "not active"; } cout << endl; cout << "Redirect noanswer: "; if (phone->ref_service(active_user)->get_cf_active(CF_NOANSWER, cf_dest)) { for (list::iterator i = cf_dest.begin(); i != cf_dest.end(); i++) { if (i != cf_dest.begin()) cout << ", "; cout << i->encode(); } } else { cout << "not active"; } cout << endl; cout << endl; return; } // Enable/disable permanent redirections if (type_present) { if (enable) { phone->ref_service(active_user)->enable_cf(cf_type, dest_list); cout << "Redirection enabled.\n\n"; } else { phone->ref_service(active_user)->disable_cf(cf_type); cout << "Redirection disabled.\n\n"; } return; } else { if (action_present) { if (!enable) { phone->ref_service(active_user)->disable_cf(CF_ALWAYS); phone->ref_service(active_user)->disable_cf(CF_BUSY); phone->ref_service(active_user)->disable_cf(CF_NOANSWER); cout << "All redirections disabled.\n\n"; return; } return; } } // Redirect current call cb_stop_call_notification(phone->get_active_line()); phone->pub_redirect(dest_list, 302); cout << endl; cout << "Line " << phone->get_active_line() + 1 << ": call redirected.\n"; cout << endl; } bool t_userintf::exec_dnd(const list command_list) { list al; bool show_status = false; bool toggle = true; bool enable = false; if (!parse_args(command_list, al)) { exec_command("help dnd"); return false; } for (list::iterator i = al.begin(); i != al.end(); i++) { switch (i->flag) { case 's': show_status = true; break; case 'a': if (i->value == "on") { enable = true; } else if (i->value == "off") { enable = false; } else { exec_command("help dnd"); return false; } toggle = false; break; default: exec_command("help dnd"); return false; break; } } do_dnd(show_status, toggle, enable); return true; } void t_userintf::do_dnd(bool show_status, bool toggle, bool enable) { if (show_status) { cout << endl; cout << "Do not disturb: "; if (phone->ref_service(active_user)->is_dnd_active()) { cout << "active"; } else { cout << "not active"; } cout << endl; return; } if (toggle) { enable = !phone->ref_service(active_user)->is_dnd_active(); } if (enable) { phone->ref_service(active_user)->enable_dnd(); cout << "Do not disturb enabled.\n\n"; return; } else { phone->ref_service(active_user)->disable_dnd(); cout << "Do not disturb disabled.\n\n"; return; } } bool t_userintf::exec_auto_answer(const list command_list) { list al; bool show_status = false; bool toggle = true; bool enable = false; if (!parse_args(command_list, al)) { exec_command("help auto_answer"); return false; } for (list::iterator i = al.begin(); i != al.end(); i++) { switch (i->flag) { case 's': show_status = true; break; case 'a': if (i->value == "on") { enable = true; } else if (i->value == "off") { enable = false; } else { exec_command("help auto_answer"); return false; } toggle = false; break; default: exec_command("help auto_answer"); return false; break; } } do_auto_answer(show_status, toggle, enable); return true; } void t_userintf::do_auto_answer(bool show_status, bool toggle, bool enable) { if (show_status) { cout << endl; cout << "Auto answer: "; if (phone->ref_service(active_user)->is_auto_answer_active()) { cout << "active"; } else { cout << "not active"; } cout << endl; return; } if (toggle) { enable = !phone->ref_service(active_user)->is_auto_answer_active(); } if (enable) { phone->ref_service(active_user)->enable_auto_answer(true); cout << "Auto answer enabled.\n\n"; return; } else { phone->ref_service(active_user)->enable_auto_answer(false); cout << "Auto answer disabled.\n\n"; return; } } bool t_userintf::exec_bye(const list command_list) { do_bye(); return true; } void t_userintf::do_bye(void) { phone->pub_end_call(); } bool t_userintf::exec_hold(const list command_list) { do_hold(); return true; } void t_userintf::do_hold(void) { phone->pub_hold(); } bool t_userintf::exec_retrieve(const list command_list) { do_retrieve(); return true; } void t_userintf::do_retrieve(void) { phone->pub_retrieve(); } bool t_userintf::exec_refer(const list command_list, bool immediate) { list al; string destination; bool dest_set = false; t_transfer_type transfer_type = TRANSFER_BASIC; if (!parse_args(command_list, al)) { exec_command("help transfer"); return false; } for (list::iterator i = al.begin(); i != al.end(); i++) { switch (i->flag) { case 'c': if (transfer_type != TRANSFER_BASIC) { exec_command("help transfer"); return false; } transfer_type = TRANSFER_CONSULT; if (!i->value.empty()) { destination = i->value; dest_set = true; } break; case 'l': if (transfer_type != TRANSFER_BASIC) { exec_command("help transfer"); return false; } transfer_type = TRANSFER_OTHER_LINE; break; case 0: destination = i->value; dest_set = true; break; default: exec_command("help transfer"); return false; break; } } if (!dest_set && transfer_type == TRANSFER_BASIC) { exec_command("help transfer"); return false; } return do_refer(destination, transfer_type, immediate); } bool t_userintf::do_refer(const string &destination, t_transfer_type transfer_type, bool immediate) { t_url dest_url; if (transfer_type == TRANSFER_BASIC || (transfer_type == TRANSFER_CONSULT && !destination.empty())) { dest_url.set_url(expand_destination(active_user, destination)); if (!dest_url.is_valid()) { exec_command("help transfer"); return false; } } unsigned short active_line; unsigned short other_line; unsigned short line_to_be_transferred; switch (transfer_type) { case TRANSFER_BASIC: phone->pub_refer(dest_url, ""); break; case TRANSFER_CONSULT: if (destination.empty()) { active_line = phone->get_active_line(); if (!phone->is_line_transfer_consult(active_line, line_to_be_transferred)) { // There is no call to transfer return false; } phone->pub_refer(line_to_be_transferred, active_line); } else { phone->pub_setup_consultation_call(dest_url, ""); } break; case TRANSFER_OTHER_LINE: active_line = phone->get_active_line(); other_line = (active_line == 0 ? 1 : 0); phone->pub_refer(active_line, other_line); break; } return true; } bool t_userintf::exec_conference(const list command_list) { do_conference(); return true; } void t_userintf::do_conference(void) { if (phone->join_3way(0, 1)) { cout << endl; cout << "Started 3-way conference.\n"; cout << endl; } else { cout << endl; cout << "Failed to start 3-way conference.\n"; cout << endl; } } bool t_userintf::exec_mute(const list command_list) { list al; bool show_status = false; bool toggle = true; bool enable = true; if (!parse_args(command_list, al)) { exec_command("help mute"); return false; } for (list::iterator i = al.begin(); i != al.end(); i++) { switch (i->flag) { case 's': show_status = true; break; case 'a': if (i->value == "on") { enable = true; } else if (i->value == "off") { enable = false; } else { exec_command("help mute"); return false; } toggle = false; break; default: exec_command("help mute"); return false; break; } } do_mute(show_status, toggle, enable); return true; } void t_userintf::do_mute(bool show_status, bool toggle, bool enable) { if (show_status) { cout << endl; cout << "Line is "; if (phone->is_line_muted(phone->get_active_line())) { cout << "muted."; } else { cout << "not muted."; } cout << endl; return; } if (toggle) enable = !phone->is_line_muted(phone->get_active_line()); if (enable) { phone->mute(enable); cout << "Line muted.\n\n"; return; } else { phone->mute(enable); cout << "Line unmuted.\n\n"; return; } } bool t_userintf::exec_dtmf(const list command_list) { list al; string digits; bool raw_mode = false; if (phone->get_line_state(phone->get_active_line()) == LS_IDLE) { return false; } if (!parse_args(command_list, al)) { exec_command("help dtmf"); return false; } for (list::iterator i = al.begin(); i != al.end(); i++) { switch (i->flag) { case 'r': raw_mode = true; if (!i->value.empty()) digits = i->value; break; case 0: digits = i->value; break; default: exec_command("help dtmf"); return false; break; } } if (!raw_mode) { digits = str2dtmf(digits); } if (digits == "") { exec_command("help dtmf"); return false; } do_dtmf(digits); return true; } void t_userintf::do_dtmf(const string &digits) { const t_call_info call_info = phone->get_call_info(phone->get_active_line()); throttle_dtmf_not_supported = false; if (!call_info.dtmf_supported) return; for (string::const_iterator i = digits.begin(); i != digits.end(); i++) { if (is_valid_dtmf_sym(*i)) { phone->pub_send_dtmf(*i, call_info.dtmf_inband, call_info.dtmf_info); } } } bool t_userintf::exec_register(const list command_list) { list al; bool reg_all_profiles = false; if (!parse_args(command_list, al)) { exec_command("help register"); return false; } for (list::iterator i = al.begin(); i != al.end(); i++) { switch (i->flag) { case 'a': reg_all_profiles = true; break; default: exec_command("help register"); return false; break; } } do_register(reg_all_profiles); return true; } void t_userintf::do_register(bool reg_all_profiles) { if (reg_all_profiles) { list user_list = phone->ref_users(); for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { phone->pub_registration(*i, REG_REGISTER, DUR_REGISTRATION(*i)); } } else { phone->pub_registration(active_user, REG_REGISTER, DUR_REGISTRATION(active_user)); } } bool t_userintf::exec_deregister(const list command_list) { list al; bool dereg_all_devices = false; bool dereg_all_profiles = false; if (!parse_args(command_list, al)) { exec_command("help deregister"); return false; } for (list::iterator i = al.begin(); i != al.end(); i++) { switch (i->flag) { case 'a': dereg_all_profiles = true; break; case 'd': dereg_all_devices = true; break; default: exec_command("help deregister"); return false; break; } } do_deregister(dereg_all_profiles, dereg_all_devices); return true; } void t_userintf::do_deregister(bool dereg_all_profiles, bool dereg_all_devices) { t_register_type dereg_type = REG_DEREGISTER; if (dereg_all_devices) { dereg_type = REG_DEREGISTER_ALL; } if (dereg_all_profiles) { list user_list = phone->ref_users(); for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { phone->pub_registration(*i, dereg_type); } } else { phone->pub_registration(active_user, dereg_type); } } bool t_userintf::exec_fetch_registrations(const list command_list) { do_fetch_registrations(); return true; } void t_userintf::do_fetch_registrations(void) { phone->pub_registration(active_user, REG_QUERY); } bool t_userintf::exec_options(const list command_list, bool immediate) { list al; string destination; bool dest_set = false; if (!parse_args(command_list, al)) { exec_command("help options"); return false; } for (list::iterator i = al.begin(); i != al.end(); i++) { switch (i->flag) { case 0: destination = i->value; dest_set = true; break; default: exec_command("help options"); return false; break; } } if (!dest_set) { if (phone->get_line_state(phone->get_active_line()) == LS_IDLE) { exec_command("help options"); return false; } } return do_options(dest_set, destination, immediate); } bool t_userintf::do_options(bool dest_set, const string &destination, bool immediate) { if (!dest_set) { phone->pub_options(); } else { t_url dest_url; dest_url.set_url(expand_destination(active_user, destination)); if (!dest_url.is_valid()) { exec_command("help options"); return false; } phone->pub_options(active_user, dest_url); } return true; } bool t_userintf::exec_line(const list command_list) { list al; int line = 0; if (!parse_args(command_list, al)) { exec_command("help line"); return false; } for (list::iterator i = al.begin(); i != al.end(); i++) { switch (i->flag) { case 0: line = atoi(i->value.c_str()); break; default: exec_command("help line"); return false; break; } } if (line < 0 || line > 2) { exec_command("help line"); return false; } do_line(line); return true; } void t_userintf::do_line(int line) { if (line == 0) { cout << endl; cout << "Active line is: " << phone->get_active_line()+1 << endl; cout << endl; return; } int current = phone->get_active_line(); if (line == current + 1) { cout << endl; cout << "Line " << current + 1 << " is already active.\n"; cout << endl; return; } phone->pub_activate_line(line - 1); if (phone->get_active_line() == current) { cout << endl; cout << "Current call cannot be put on-hold.\n"; cout << "Cannot switch to another line now.\n"; cout << endl; } else { cout << endl; cout << "Line " << phone->get_active_line()+1 << " is now active.\n"; cout << endl; } } bool t_userintf::exec_user(const list command_list) { list al; string profile_name; if (!parse_args(command_list, al)) { exec_command("help user"); return false; } for (list::iterator i = al.begin(); i != al.end(); i++) { switch (i->flag) { case 0: profile_name = i->value; break; default: exec_command("help user"); return false; break; } } do_user(profile_name); return true; } void t_userintf::do_user(const string &profile_name) { list user_list = phone->ref_users(); if (profile_name.empty()) { // Show all users cout << endl; for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { if (*i == active_user) { cout << "* "; } else { cout << " "; } cout << (*i)->get_profile_name(); cout << "\n "; cout << (*i)->get_display(false); cout << " get_name(); cout << "@" << (*i)->get_domain() << ">\n"; } cout << endl; return; } for (list::iterator i = user_list.begin(); i != user_list.end(); i++) { if ((*i)->get_profile_name() == profile_name) { active_user = (*i); cout << endl; cout << profile_name; cout << " activated.\n"; cout << endl; return; } } cout << endl; cout << "Unknown user profile: "; cout << profile_name; cout << endl << endl; } bool t_userintf::exec_zrtp(const list command_list) { list al; t_zrtp_cmd zrtp_cmd = ZRTP_ENCRYPT; if (!parse_args(command_list, al)) { exec_command("help zrtp"); return false; } if (al.size() != 1) { exec_command("help zrtp"); return false; } for (list::iterator i = al.begin(); i != al.end(); i++) { switch (i->flag) { case 0: if (i->value == "encrypt") { zrtp_cmd = ZRTP_ENCRYPT; } else if (i->value == "go-clear") { zrtp_cmd = ZRTP_GO_CLEAR; } else if (i->value == "confirm-sas") { zrtp_cmd = ZRTP_CONFIRM_SAS; } else if (i->value == "reset-sas") { zrtp_cmd = ZRTP_RESET_SAS; } else { exec_command("help zrtp"); return false; } break; default: exec_command("help zrtp"); return false; break; } } do_zrtp(zrtp_cmd); return true; } void t_userintf::do_zrtp(t_zrtp_cmd zrtp_cmd) { switch (zrtp_cmd) { case ZRTP_ENCRYPT: phone->pub_enable_zrtp(); break; case ZRTP_GO_CLEAR: phone->pub_zrtp_request_go_clear(); break; case ZRTP_CONFIRM_SAS: phone->pub_confirm_zrtp_sas(); break; case ZRTP_RESET_SAS: phone->pub_reset_zrtp_sas_confirmation(); break; default: assert(false); } } bool t_userintf::exec_message(const list command_list) { list al; string display; string subject; string filename; string destination; string text; if (!parse_args(command_list, al)) { exec_command("help message"); return false; } for (list::iterator i = al.begin(); i != al.end(); i++) { switch (i->flag) { case 's': subject = i->value; break; case 'f': filename = i->value; break; case 'd': display = i->value; break; case 0: if (destination.empty()) { destination = i->value; } else { text = i->value; } break; default: exec_command("help message"); return false; break; } } if (destination.empty() || (text.empty() && filename.empty())) { exec_command("help message"); return false; } im::t_msg msg(text, im::MSG_DIR_OUT, im::TXT_PLAIN); msg.subject = subject; if (!filename.empty()) { t_media media("application/octet-stream"); string mime_type = mime_database->get_mimetype(filename); if (!mime_type.empty()) { media = t_media(mime_type); } msg.set_attachment(filename, media, strip_path_from_filename(filename)); } return do_message(destination, display, msg); } bool t_userintf::do_message(const string &destination, const string &display, const im::t_msg &msg) { t_url dest_url(expand_destination(active_user, destination)); if (!dest_url.is_valid()) { exec_command("help message"); return false; } (void)phone->pub_send_message(active_user, dest_url, display, msg); return true; } bool t_userintf::exec_presence(const list command_list) { list al; t_presence_state::t_basic_state basic_state = t_presence_state::ST_BASIC_OPEN; if (!parse_args(command_list, al)) { exec_command("help presence"); return false; } for (list::iterator i = al.begin(); i != al.end(); i++) { switch (i->flag) { case 'b': if (i->value == "online") { basic_state = t_presence_state::ST_BASIC_OPEN; } else if (i->value == "offline") { basic_state = t_presence_state::ST_BASIC_CLOSED; } else { exec_command("help presence"); return false; } break; default: exec_command("help presence"); return false; break; } } do_presence(basic_state); return true; } void t_userintf::do_presence(t_presence_state::t_basic_state basic_state) { phone->pub_publish_presence(active_user, basic_state); } bool t_userintf::exec_quit(const list command_list) { do_quit(); return true; } void t_userintf::do_quit(void) { end_interface = true; } bool t_userintf::exec_help(const list command_list) { list al; if (!parse_args(command_list, al)) { exec_command("help help"); return false; } if (al.size() > 1) { exec_command("help help"); return false; } do_help(al); return true; } void t_userintf::do_help(const list &al) { if (al.size() == 0) { cout << endl; cout << "call Call someone\n"; cout << "answer Answer an incoming call\n"; cout << "answerbye Answer an incoming call or end a call\n"; cout << "reject Reject an incoming call\n"; cout << "redirect Redirect an incoming call\n"; cout << "transfer Transfer a standing call\n"; cout << "bye End a call\n"; cout << "hold Put a call on-hold\n"; cout << "retrieve Retrieve a held call\n"; cout << "conference Join 2 calls in a 3-way conference\n"; cout << "mute Mute a line\n"; cout << "dtmf Send DTMF\n"; cout << "redial Repeat last call\n"; cout << "register Register your phone at a registrar\n"; cout << "deregister De-register your phone at a registrar\n"; cout << "fetch_reg Fetch registrations from registrar\n"; cout << "options\t\tGet capabilities of another SIP endpoint\n"; cout << "line Toggle between phone lines\n"; cout << "dnd Do not disturb\n"; cout << "auto_answer Auto answer\n"; cout << "user Show users / set active user\n"; #ifdef HAVE_ZRTP cout << "zrtp ZRTP command for voice encryption\n"; #endif cout << "message\t\tSend an instant message\n"; cout << "presence Publish your presence state\n"; cout << "quit Quit\n"; cout << "help Get help on a command\n"; cout << endl; return; } bool ambiguous; string c = complete_command(tolower(al.front().value), ambiguous); if (c == "call") { cout << endl; cout << "Usage:\n"; cout << "\tcall [-s subject] [-d display] [-h] dst\n"; cout << "Description:\n"; cout << "\tCall someone.\n"; cout << "Arguments:\n"; cout << "\t-s subject Add a subject header to the INVITE\n"; cout << "\t-d display Add display name to To-header\n"; cout << "\t-h Hide your identity\n"; cout << "\tdst SIP uri of party to invite\n"; cout << endl; return; } if (c == "answer") { cout << endl; cout << "Usage:\n"; cout << "\tanswer\n"; cout << "Description:\n"; cout << "\tAnswer an incoming call.\n"; cout << endl; return; } if (c == "answerbye") { cout << endl; cout << "Usage:\n"; cout << "\tanswerbye\n"; cout << "Description:\n"; cout << "\tWith this command you can answer an incoming call or\n"; cout << "\tend an established call.\n"; cout << endl; return; } if (c == "reject") { cout << endl; cout << "Usage:\n"; cout << "\treject\n"; cout << "Description:\n"; cout << "\tReject an incoming call. A 603 Decline response\n"; cout << "\twill be sent.\n"; cout << endl; return; } if (c == "redirect") { cout << endl; cout << "Usage:\n"; cout << "\tredirect [-s] [-t type] [-a on|off] [dst ... dst]\n"; cout << "Description:\n"; cout << "\tRedirect an incoming call. A 302 Moved Temporarily\n"; cout << "\tresponse will be sent.\n"; cout << "\tYou can redirect the current incoming call by specifying\n"; cout << "\tone or more destinations without any other arguments.\n"; cout << "Arguments:\n"; cout << "\t-s Show which redirections are active.\n"; cout << "\t-t type\t Type for permanent redirection of calls.\n"; cout << "\t Values: always, busy, noanswer.\n"; cout << "\t-a on|off Enable/disable permanent redirection.\n"; cout << "\t The default action is 'on'.\n"; cout << "\t You can disable all redirections with the\n"; cout << "\t 'off' action and no type.\n"; cout << "\tdst SIP uri where the call should be redirected.\n"; cout << "\t You can specify up to 5 destinations.\n"; cout << "\t The destinations will be tried in sequence.\n"; cout << "Examples:\n"; cout << "\tRedirect current incoming call to michel@twinklephone.com\n"; cout << "\tredirect michel@twinklephone.com\n"; cout << endl; cout << "\tRedirect busy calls permanently to michel@twinklephone.com\n"; cout << "\tredirect -t busy michel@twinklephone.com\n"; cout << endl; cout << "\tDisable redirection of busy calls.\n"; cout << "\tredirect -t busy -a off\n"; cout << endl; return; } if (c == "transfer") { cout << endl; cout << "Usage:\n"; cout << "\ttransfer [-c] [-l] [dst]\n"; cout << "Description:\n"; cout << "\tTransfer a standing call to another destination.\n"; cout << "\tFor a transfer with consultation, first use the -c flag with a\n"; cout << "\tdestination. This sets up the consultation call. When the\n"; cout << "\tconsulted party agrees, give the command with the -c flag once\n"; cout << "\tmore, but now without a destination. This transfers the call.\n"; cout << "Arguments:\n"; cout << "\t-c Consult destination before transferring call.\n"; cout << "\t-l Transfer call to party on other line.\n"; cout << "\tdst SIP uri of transfer destination\n"; cout << endl; return; } if (c == "bye") { cout << endl; cout << "Usage:\n"; cout << "\tbye\n"; cout << "Description:\n"; cout << "\tEnd a call.\n"; cout << "\tFor a stable call a BYE will be sent.\n"; cout << "\tIf the invited party did not yet sent a final answer,\n"; cout << "\tthen a CANCEL will be sent.\n"; cout << endl; return; } if (c == "hold") { cout << endl; cout << "Usage:\n"; cout << "\thold\n"; cout << "Description:\n"; cout << "\tPut the current call on the acitve line on-hold.\n"; cout << endl; return; } if (c == "retrieve") { cout << endl; cout << "Usage:\n"; cout << "\tretrieve\n"; cout << "Description:\n"; cout << "\tRetrieve a held call on the active line.\n"; cout << endl; return; } if (c == "conference") { cout << endl; cout << "Usage:\n"; cout << "\tconference\n"; cout << "Description:\n"; cout << "\tJoin 2 calls in a 3-way conference. Before you give this\n"; cout << "\tcommand you must have a call on each line.\n"; cout << endl; return; } if (c == "mute") { cout << endl; cout << "Usage:\n"; cout << "\tmute [-s] [-a on|off]\n"; cout << "Description:\n"; cout << "\tMute/unmute the active line.\n"; cout << "\tYou can hear the other side of the line, but they cannot\n"; cout << "\thear you.\n"; cout << "Arguments:\n"; cout << "\t-s Show if line is muted.\n"; cout << "\t-a on|off Mute/unmute.\n"; cout << "Notes:\n"; cout << "\tWithout any arguments you can toggle the status.\n"; cout << endl; return; } if (c == "dtmf") { cout << endl; cout << "Usage:\n"; cout << "\tdtmf digits\n"; cout << "Description:\n"; cout << "\tSend the digits as out-of-band DTMF telephone events "; cout << "(RFC 2833).\n"; cout << "\tThis command can only be given when a call is "; cout << "established.\n"; cout << "Arguments:\n"; cout << "\t-r Raw mode: do not convert letters to digits.\n"; cout << "\tdigits 0-9 | A-D | * | #\n"; cout << "Example:\n"; cout << "\tdtmf 1234#\n"; cout << "\tdmtf movies\n"; cout << "Notes:\n"; cout << "\tThe overdecadic digits A-D can only be sent in raw mode.\n"; cout << endl; return; } if (c == "redial") { cout << endl; cout << "Usage:\n"; cout << "\tredial\n"; cout << "Description:\n"; cout << "\tRepeat last call.\n"; cout << endl; return; } if (c == "register") { cout << endl; cout << "Usage:\n"; cout << "\tregister\n"; cout << "Description:\n"; cout << "\tRegister your phone at a registrar.\n"; cout << "Arguments:\n"; cout << "\t-a Register all enabled user profiles.\n"; cout << endl; return; } if (c == "deregister") { cout << endl; cout << "Usage:\n"; cout << "\tderegister [-a]\n"; cout << "Description:\n"; cout << "\tDe-register your phone at a registrar.\n"; cout << "Arguments:\n"; cout << "\t-a De-register all enabled user profiles.\n"; cout << "\t-d De-register all devices.\n"; cout << endl; return; } if (c == "fetch_reg") { cout << endl; cout << "Usage:\n"; cout << "\tfetch_reg\n"; cout << "Description:\n"; cout << "\tFetch current registrations from registrar.\n"; cout << endl; return; } if (c == "options") { cout << endl; cout << "Usage:\n"; cout << "\toptions [dst]\n"; cout << "Description:\n"; cout << "\tGet capabilities of another SIP endpoint.\n"; cout << "\tIf no destination is passed as an argument, then\n"; cout << "\tthe capabilities of the far-end in the current call\n"; cout << "\ton the active line are requested.\n"; cout << "Arguments:\n"; cout << "\tdst SIP uri of end-point\n"; cout << endl; return; } if (c == "line") { cout << endl; cout << "Usage:\n"; cout << "\tline [lineno]\n"; cout << "Description:\n"; cout << "\tIf no argument is passed then the current active "; cout << "line is shown\n"; cout << "\tOtherwise switch to another line. If the current active\n"; cout << "\thas a call, then this call will be put on-hold.\n"; cout << "\tIf the new active line has a held call, then this call\n"; cout << "\twill be retrieved.\n"; cout << "Arguments:\n"; cout << "\tlineno Switch to another line (values = "; cout << "1,2)\n"; cout << endl; return; } if (c == "dnd") { cout << endl; cout << "Usage:\n"; cout << "\tdnd [-s] [-a on|off]\n"; cout << "Description:\n"; cout << "\tEnable/disable the do not disturb service.\n"; cout << "\tIf dnd is enabled then a 480 Temporarily Unavailable "; cout << "response is given\n"; cout << "\ton incoming calls.\n"; cout << "Arguments:\n"; cout << "\t-s Show if dnd is active.\n"; cout << "\t-a on|off Enable/disable dnd.\n"; cout << "Notes:\n"; cout << "\tWithout any arguments you can toggle the status.\n"; cout << endl; return; } if (c == "auto_answer") { cout << endl; cout << "Usage:\n"; cout << "\tauto_answer [-s] [-a on|off]\n"; cout << "Description:\n"; cout << "\tEnable/disable the auto answer service.\n"; cout << "Arguments:\n"; cout << "\t-s Show if auto answer is active.\n"; cout << "\t-a on|off Enable/disable auto answer.\n"; cout << "Notes:\n"; cout << "\tWithout any arguments you can toggle the status.\n"; cout << endl; return; } if (c == "user") { cout << endl; cout << "Usage:\n"; cout << "\tuser [profile name]\n"; cout << "Description:\n"; cout << "\tMake a user profile the active profile.\n"; cout << "\tCommands like 'invite' are executed for the active profile.\n"; cout << "\tWithout an argument this command lists all users. The active\n"; cout << "\tuser will be marked with '*'.\n"; cout << "Arguments:\n"; cout << "\tprofile name The user profile to activate.\n"; cout << endl; return; } #ifdef HAVE_ZRTP if (c == "zrtp") { cout << endl; cout << "Usage:\n"; cout << "\tzrtp \n"; cout << "Description:\n"; cout << "\tExecute a ZRTP command.\n"; cout << "ZRTP commands:\n"; cout << "\tencrypt Start ZRTP negotiation for encryption.\n"; cout << "\tgo-clear Send ZRTP go-clear request.\n"; cout << "\tconfirm-sas Confirm the SAS value.\n"; cout << "\treset-sas Reset SAS confirmation.\n"; cout << endl; return; } #endif if (c == "message") { cout << endl; cout << "Usage:\n"; cout << "\tmessage [-s subject] [-f file name] [-d display] dst [text]\n"; cout << "Description:\n"; cout << "\tSend an instant message.\n"; cout << "Arguments:\n"; cout << "\t-s subject Subject of the message.\n"; cout << "\t-f file name File name of the file to send.\n"; cout << "\t-d display Add display name to To-header\n"; cout << "\tdst SIP uri of party to message\n"; cout << "\ttext Message text to send. Surround with double quotes\n"; cout << "\t\t\twhen your text contains whitespace.\n"; cout << "\t\t\tWhen you send a file, then the text is ignored.\n"; cout << endl; return; } if (c == "presence") { cout << endl; cout << "Usage:\n"; cout << "\tpresence -b [online|offline]\n"; cout << "Description:\n"; cout << "\tPublish your presence state to a presence agent\n"; cout << "Arguments:\n"; cout << "\t-b A basic presence state: online or offline\n"; cout << endl; return; } if (c == "quit") { cout << endl; cout << "Usage:\n"; cout << "\tquit\n"; cout << "Description:\n"; cout << "\tQuit.\n"; cout << endl; return; } if (c == "help") { cout << endl; cout << "Usage:\n"; cout << "\thelp [command]\n"; cout << "Description:\n"; cout << "\tShow help on a command.\n"; cout << "Arguments:\n"; cout << "\tcommand Command you want help with\n"; cout << endl; return; } cout << endl; cout << "\nUnknown command\n\n"; cout << endl; } ///////////////////////////// // Public ///////////////////////////// t_userintf::t_userintf(t_phone *_phone) { phone = _phone; end_interface = false; tone_gen = NULL; active_user = NULL; use_stdout = true; throttle_dtmf_not_supported = false; thr_process_events = NULL; all_commands.push_back("invite"); all_commands.push_back("call"); all_commands.push_back("answer"); all_commands.push_back("answerbye"); all_commands.push_back("reject"); all_commands.push_back("redirect"); all_commands.push_back("bye"); all_commands.push_back("hold"); all_commands.push_back("retrieve"); all_commands.push_back("refer"); all_commands.push_back("transfer"); all_commands.push_back("conference"); all_commands.push_back("mute"); all_commands.push_back("dtmf"); all_commands.push_back("redial"); all_commands.push_back("register"); all_commands.push_back("deregister"); all_commands.push_back("fetch_reg"); all_commands.push_back("options"); all_commands.push_back("line"); all_commands.push_back("dnd"); all_commands.push_back("auto_answer"); all_commands.push_back("user"); #ifdef HAVE_ZRTP all_commands.push_back("zrtp"); #endif all_commands.push_back("message"); all_commands.push_back("presence"); all_commands.push_back("quit"); all_commands.push_back("exit"); all_commands.push_back("q"); all_commands.push_back("x"); all_commands.push_back("help"); all_commands.push_back("h"); all_commands.push_back("?"); } t_userintf::~t_userintf() { if (tone_gen) { MEMMAN_DELETE(tone_gen); delete tone_gen; } if (thr_process_events) { evq_ui_events.push_quit(); thr_process_events->join(); log_file->write_report("thr_process_events stopped.", "t_userintf::~t_userintf", LOG_NORMAL, LOG_DEBUG); MEMMAN_DELETE(thr_process_events); delete thr_process_events; } } string t_userintf::complete_command(const string &c, bool &ambiguous) { ambiguous = false; string full_command; for (list::const_iterator i = all_commands.begin(); i != all_commands.end(); i++) { // If there is an exact match, then this is the command. // This allows a one command to be a prefix of another command. if (c == *i) { ambiguous = false; return c; } if (c.size() < i->size() && c == i->substr(0, c.size())) { if (full_command != "") { ambiguous = true; // Do not return here, as there might still be // an exact match. } full_command = *i; } } if (ambiguous) return ""; return full_command; } bool t_userintf::exec_command(const string &command_line, bool immediate) { vector v = split_ws(command_line, true); if (v.size() == 0) return false; bool ambiguous; string command = complete_command(tolower(v[0]), ambiguous); if (ambiguous) { if (use_stdout) { cout << endl; cout << "Ambiguous command\n"; cout << endl; } return false; } list l(v.begin(), v.end()); if (command == "invite") return exec_invite(l, immediate); if (command == "call") return exec_invite(l, immediate); if (command == "answer") return exec_answer(l); if (command == "answerbye") return exec_answerbye(l); if (command == "reject") return exec_reject(l); if (command == "redirect") return exec_redirect(l, immediate); if (command == "bye") return exec_bye(l); if (command == "hold") return exec_hold(l); if (command == "retrieve") return exec_retrieve(l); if (command == "refer") return exec_refer(l, immediate); if (command == "transfer") return exec_refer(l, immediate); if (command == "conference") return exec_conference(l); if (command == "mute") return exec_mute(l); if (command == "dtmf") return exec_dtmf(l); if (command == "redial") return exec_redial(l); if (command == "register") return exec_register(l); if (command == "deregister") return exec_deregister(l); if (command == "fetch_reg") return exec_fetch_registrations(l); if (command == "options") return exec_options(l, immediate); if (command == "line") return exec_line(l); if (command == "dnd") return exec_dnd(l); if (command == "auto_answer") return exec_auto_answer(l); if (command == "user") return exec_user(l); #ifdef HAVE_ZRTP if (command == "zrtp") return exec_zrtp(l); #endif if (command == "message") return exec_message(l); if (command == "presence") return exec_presence(l); if (command == "quit") return exec_quit(l); if (command == "exit") return exec_quit(l); if (command == "x") return exec_quit(l); if (command == "q") return exec_quit(l); if (command == "help") return exec_help(l); if (command == "h") return exec_help(l); if (command == "?") return exec_help(l); if (use_stdout) { cout << endl; cout << "Unknown command\n"; cout << endl; } return false; } string t_userintf::format_sip_address(t_user *user_config, const string &display, const t_url &uri) const { string s; if (uri.encode() == ANONYMOUS_URI) { return TRANSLATE("Anonymous"); } s = display; if (display != "") s += " <"; string number; if (uri.get_scheme() == "tel") { number = uri.get_host(); } else { number = uri.get_user(); } if (user_config->get_display_useronly_phone() && uri.is_phone(user_config->get_numerical_user_is_phone(), user_config->get_special_phone_symbols())) { // Display telephone number only s += user_config->convert_number(number); } else { // Display full URI // Convert the username according to the number conversion // rules. t_url u(uri); string username = user_config->convert_number(number); if (username != number) { if (uri.get_scheme() == "tel") { u.set_host(username); } else { u.set_user(username); } } s += u.encode_no_params_hdrs(false); } if (display != "") s += ">"; return s; } list t_userintf::format_warnings(const t_hdr_warning &hdr_warning) const { string s; list l; for (list::const_iterator i = hdr_warning.warnings.begin(); i != hdr_warning.warnings.end(); i++) { s = TRANSLATE("Warning:"); s += " "; s += int2str(i->code); s += ' '; s += i->text; s += " ("; s += i->host; if (i->port > 0) s += int2str(i->port, ":%d"); s += ')'; l.push_back(s); } return l; } string t_userintf::format_codec(t_audio_codec codec) const { switch (codec) { case CODEC_NULL: return "null"; case CODEC_UNSUPPORTED: return "???"; case CODEC_G711_ALAW: return "g711a"; case CODEC_G711_ULAW: return "g711u"; case CODEC_GSM: return "gsm"; case CODEC_SPEEX_NB: return "spx-nb"; case CODEC_SPEEX_WB: return "spx-wb"; case CODEC_SPEEX_UWB: return "spx-uwb"; case CODEC_ILBC: return "ilbc"; case CODEC_G726_16: return "g726-16"; case CODEC_G726_24: return "g726-24"; case CODEC_G726_32: return "g726-32"; case CODEC_G726_40: return "g726-40"; case CODEC_G729A: return "g729a"; default: return "???"; } } void t_userintf::run(void) { // Start asynchronous event processor thr_process_events = new t_thread(process_events_main, NULL); MEMMAN_NEW(thr_process_events); list user_list = phone->ref_users(); active_user = user_list.front(); cout << PRODUCT_NAME << " " << PRODUCT_VERSION << ", " << PRODUCT_DATE; cout << endl; cout << "Copyright (C) 2005-2015 " << PRODUCT_AUTHOR << endl; cout << endl; cout << "Users:"; exec_command("user"); cout << "Local IP: " << user_host << endl; cout << endl; restore_state(); // Initialize phone functions phone->init(); //Initialize GNU readline functions rl_attempted_completion_function = tw_completion; using_history(); read_history(sys_config->get_history_file().c_str()); stifle_history(CLI_MAX_HISTORY_LENGTH); while (!end_interface) { char *command_line = tw_readline(CLI_PROMPT); if (!command_line){ cout << endl; break; } exec_command(command_line); } // Terminate phone functions write_history(sys_config->get_history_file().c_str()); phone->terminate(); save_state(); cout << endl; } void t_userintf::run_on_event_queue(std::function fn) { evq_ui_events.push_fncall(fn); } void t_userintf::process_events(void) { t_event *event; t_event_ui *ui_event; bool quit = false; while (!quit) { event = evq_ui_events.pop(); switch (event->get_type()) { case EV_UI: ui_event = dynamic_cast(event); assert(ui_event); ui_event->exec(this); break; case EV_QUIT: quit = true; break; case EV_FN_CALL: static_cast(event)->invoke(); break; default: assert(false); break; } MEMMAN_DELETE(event); delete event; } } void t_userintf::save_state(void) { string err_msg; sys_config->set_redial_url(last_called_url); sys_config->set_redial_display(last_called_display); sys_config->set_redial_subject(last_called_subject); sys_config->set_redial_profile(last_called_profile); sys_config->set_redial_hide_user(last_called_hide_user); sys_config->write_config(err_msg); } void t_userintf::restore_state(void) { last_called_url = sys_config->get_redial_url(); last_called_display = sys_config->get_redial_display(); last_called_subject = sys_config->get_redial_subject(); last_called_profile = sys_config->get_redial_profile(); last_called_hide_user = sys_config->get_redial_hide_user(); } void t_userintf::lock(void) { assert(!is_prohibited_thread()); // TODO: lock for CLI } void t_userintf::unlock(void) { // TODO: lock for CLI } string t_userintf::select_network_intf(void) { string ip; list *l = get_interfaces(); // As memman has no hooks in the socket routines, report it here. MEMMAN_NEW(l); if (l->size() == 0) { // cout << "Cannot find a network interface\n"; cout << "Cannot find a network interface. Twinkle will use\n" "127.0.0.1 as the local IP address. When you connect to\n" "the network you have to restart Twinkle to use the correct\n" "IP address.\n"; MEMMAN_DELETE(l); delete l; return "127.0.0.1"; } if (l->size() == 1) { ip = l->front().get_ip_addr(); } else { size_t num = 1; cout << "Multiple network interfaces found.\n"; for (list::iterator i = l->begin(); i != l->end(); i++) { cout << num << ") " << i->name << ": "; cout << i->get_ip_addr() << endl; num++; } cout << endl; size_t selection = 0; while (selection < 1 || selection > l->size()) { cout << "Which interface do you want to use (enter number): "; string choice; getline(cin, choice); selection = atoi(choice.c_str()); } num = 1; for (list::iterator i = l->begin(); i != l->end(); i++) { if (num == selection) { ip = i->get_ip_addr(); break; } num++; } } MEMMAN_DELETE(l); delete l; return ip; } bool t_userintf::select_user_config(list &config_files) { // In CLI mode, simply select the default config file config_files.clear(); config_files.push_back(USER_CONFIG_FILE); return true; } void t_userintf::cb_incoming_call(t_user *user_config, int line, const t_request *r) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": "; cout << "incoming call\n"; cout << "From:\t\t"; string from_party = format_sip_address(user_config, r->hdr_from.get_display_presentation(), r->hdr_from.uri); cout << from_party << endl; if (r->hdr_organization.is_populated()) { cout << "Organization:\t" << r->hdr_organization.name << endl; } cout << "To:\t\t"; cout << format_sip_address(user_config, r->hdr_to.display, r->hdr_to.uri) << endl; if (r->hdr_referred_by.is_populated()) { cout << "Referred-by:\t"; cout << format_sip_address(user_config, r->hdr_referred_by.display, r->hdr_referred_by.uri); cout << endl; } if (r->hdr_subject.is_populated()) { cout << "Subject:\t" << r->hdr_subject.subject << endl; } cout << endl; cout.flush(); cb_notify_call(line, from_party); } void t_userintf::cb_call_cancelled(int line) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": "; cout << "far end cancelled call.\n"; cout << endl; cout.flush(); cb_stop_call_notification(line); } void t_userintf::cb_far_end_hung_up(int line) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": "; cout << "far end ended call.\n"; cout << endl; cout.flush(); cb_stop_call_notification(line); } void t_userintf::cb_answer_timeout(int line) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": "; cout << "answer timeout.\n"; cout << endl; cout.flush(); cb_stop_call_notification(line); } void t_userintf::cb_sdp_answer_not_supported(int line, const string &reason) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": "; cout << "SDP answer from far end not supported.\n"; cout << reason << endl; cout << endl; cout.flush(); cb_stop_call_notification(line); } void t_userintf::cb_sdp_answer_missing(int line) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": "; cout << "SDP answer from far end missing.\n"; cout << endl; cout.flush(); cb_stop_call_notification(line); } void t_userintf::cb_unsupported_content_type(int line, const t_sip_message *r) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": "; cout << "Unsupported content type in answer from far end.\n"; cout << r->hdr_content_type.media.type << "/"; cout << r->hdr_content_type.media.subtype << endl; cout << endl; cout.flush(); cb_stop_call_notification(line); } void t_userintf::cb_ack_timeout(int line) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": "; cout << "no ACK received, call will be terminated.\n"; cout << endl; cout.flush(); cb_stop_call_notification(line); } void t_userintf::cb_100rel_timeout(int line) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": "; cout << "no PRACK received, call will be terminated.\n"; cout << endl; cout.flush(); cb_stop_call_notification(line); } void t_userintf::cb_prack_failed(int line, const t_response *r) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": PRACK failed.\n"; cout << r->code << ' ' << r->reason << endl; cout << endl; cout.flush(); cb_stop_call_notification(line); } void t_userintf::cb_provisional_resp_invite(int line, const t_response *r) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": received "; cout << r->code << ' ' << r->reason << endl; cout << endl; cout.flush(); } void t_userintf::cb_cancel_failed(int line, const t_response *r) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": cancel failed.\n"; cout << r->code << ' ' << r->reason << endl; cout << endl; cout.flush(); } void t_userintf::cb_call_answered(t_user *user_config, int line, const t_response *r) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": far end answered call.\n"; cout << r->code << ' ' << r->reason << endl; cout << "To: "; cout << format_sip_address(user_config, r->hdr_to.display, r->hdr_to.uri) << endl; if (r->hdr_organization.is_populated()) { cout << "Organization: " << r->hdr_organization.name << endl; } cout << endl; cout.flush(); } void t_userintf::cb_call_failed(t_user *user_config, int line, const t_response *r) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": call failed.\n"; cout << r->code << ' ' << r->reason << endl; // Warnings if (r->hdr_warning.is_populated()) { list l = format_warnings(r->hdr_warning); for (list::iterator i = l.begin(); i != l.end(); i++) { cout << *i << endl; } } // Redirection response if (r->get_class() == R_3XX && r->hdr_contact.is_populated()) { list l = r->hdr_contact.contact_list; l.sort(); cout << "You can try the following contacts:\n"; for (list::iterator i = l.begin(); i != l.end(); i++) { cout << format_sip_address(user_config, i->display, i->uri) << endl; } } // Unsupported extensions if (r->code == R_420_BAD_EXTENSION) { cout << r->hdr_unsupported.encode(); } cout << endl; cout.flush(); } void t_userintf::cb_stun_failed_call_ended(int line) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": call failed.\n"; cout << endl; cout.flush(); } void t_userintf::cb_call_ended(int line) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": call ended.\n"; cout.flush(); } void t_userintf::cb_call_established(int line) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": call established.\n"; cout << endl; cout.flush(); } void t_userintf::cb_options_response(const t_response *r) { cout << endl; cout << "OPTIONS response received: "; cout << r->code << ' ' << r->reason << endl; cout << "Capabilities of " << r->hdr_to.uri.encode() << endl; cout << "Accepted body types\n"; if (r->hdr_accept.is_populated()) { cout << "\t" << r->hdr_accept.encode(); } else { cout << "\tUnknown\n"; } cout << "Accepted encodings\n"; if (r->hdr_accept_encoding.is_populated()) { cout << "\t" << r->hdr_accept_encoding.encode(); } else { cout << "\tUnknown\n"; } cout << "Accepted languages\n"; if (r->hdr_accept_language.is_populated()) { cout << "\t" << r->hdr_accept_language.encode(); } else { cout << "\tUnknown\n"; } cout << "Allowed requests\n"; if (r->hdr_allow.is_populated()) { cout << "\t" << r->hdr_allow.encode(); } else { cout << "\tUnknown\n"; } cout << "Supported extensions\n"; if (r->hdr_supported.is_populated()) { if (r->hdr_supported.features.empty()) { cout << "\tNone\n"; } else { cout << "\t" << r->hdr_supported.encode(); } } else { cout << "\tUnknown\n"; } cout << "End point type\n"; bool endpoint_known = false; if (r->hdr_server.is_populated()) { cout << "\t" << r->hdr_server.encode(); endpoint_known = true; } if (r->hdr_user_agent.is_populated()) { // Some end-point put a User-Agent header in the response // instead of a Server header. cout << "\t" << r->hdr_user_agent.encode(); endpoint_known = true; } if (!endpoint_known) { cout << "\tUnknown\n"; } cout << endl; cout.flush(); } void t_userintf::cb_reinvite_success(int line, const t_response *r) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": re-INVITE successful.\n"; cout << r->code << ' ' << r->reason << endl; cout << endl; cout.flush(); } void t_userintf::cb_reinvite_failed(int line, const t_response *r) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": re-INVITE failed.\n"; cout << r->code << ' ' << r->reason << endl; cout << endl; cout.flush(); } void t_userintf::cb_retrieve_failed(int line, const t_response *r) { if (line >= NUM_USER_LINES) return; // The status code from the response has already been reported // by cb_reinvite_failed. cout << endl; cout << "Line " << line + 1 << ": retrieve failed.\n"; cout << endl; cout.flush(); } void t_userintf::cb_invalid_reg_resp(t_user *user_config, const t_response *r, const string &reason) { cout << endl; cout << user_config->get_profile_name(); cout << ", registration failed: " << r->code << ' ' << r->reason << endl; cout << reason << endl; cout << endl; cout.flush(); } void t_userintf::cb_register_success(t_user *user_config, const t_response *r, unsigned long expires, bool first_success) { // Only report success if this is the first success in a sequence if (!first_success) return; cout << endl; cout << user_config->get_profile_name(); cout << ": registration succeeded (expires = " << expires << " seconds)\n"; // Date at registrar if (r->hdr_date.is_populated()) { cout << "Registrar "; cout << r->hdr_date.encode() << endl; } cout << endl; cout.flush(); } void t_userintf::cb_register_failed(t_user *user_config, const t_response *r, bool first_failure) { // Only report the first failure in a sequence of failures if (!first_failure) return; cout << endl; cout << user_config->get_profile_name(); cout << ", registration failed: " << r->code << ' ' << r->reason << endl; cout << endl; cout.flush(); } void t_userintf::cb_register_stun_failed(t_user *user_config, bool first_failure) { // Only report the first failure in a sequence of failures if (!first_failure) return; cout << endl; cout << user_config->get_profile_name(); cout << ", registration failed: STUN failure"; cout << endl; cout.flush(); } void t_userintf::cb_deregister_success(t_user *user_config, const t_response *r) { cout << endl; cout << user_config->get_profile_name(); cout << ", de-registration succeeded: " << r->code << ' ' << r->reason << endl; cout << endl; cout.flush(); } void t_userintf::cb_deregister_failed(t_user *user_config, const t_response *r) { cout << endl; cout << user_config->get_profile_name(); cout << ", de-registration failed: " << r->code << ' ' << r->reason << endl; cout << endl; cout.flush(); } void t_userintf::cb_fetch_reg_failed(t_user *user_config, const t_response *r) { cout << endl; cout << user_config->get_profile_name(); cout << ", fetch registrations failed: " << r->code << ' ' << r->reason << endl; cout << endl; cout.flush(); } void t_userintf::cb_fetch_reg_result(t_user *user_config, const t_response *r) { cout << endl; cout << user_config->get_profile_name(); const list &l = r->hdr_contact.contact_list; if (l.size() == 0) { cout << ": you are not registered\n"; } else { cout << ": you have the following registrations\n"; for (list::const_iterator i = l.begin(); i != l.end(); i++) { cout << i->encode() << endl; } } cout << endl; cout.flush(); } void t_userintf::cb_register_inprog(t_user *user_config, t_register_type register_type) { switch (register_type) { case REG_REGISTER: // Do not report a register refreshment if (phone->get_is_registered(user_config)) return; // Do not report an automatic register re-attempt if (phone->get_last_reg_failed(user_config)) return; cout << endl; cout << user_config->get_profile_name(); cout << ": registering phone...\n"; break; case REG_DEREGISTER: cout << endl; cout << user_config->get_profile_name(); cout << ": deregistering phone...\n"; break; case REG_DEREGISTER_ALL: cout << endl; cout << user_config->get_profile_name(); cout << ": deregistering all phones..."; break; case REG_QUERY: cout << endl; cout << user_config->get_profile_name(); cout << ": fetching registrations..."; break; default: assert(false); } cout << endl; cout.flush(); } void t_userintf::cb_redirecting_request(t_user *user_config, int line, const t_contact_param &contact) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": redirecting request to:\n"; cout << format_sip_address(user_config, contact.display, contact.uri) << endl; cout << endl; cout.flush(); } void t_userintf::cb_redirecting_request(t_user *user_config, const t_contact_param &contact) { cout << endl; cout << "Redirecting request to: "; cout << format_sip_address(user_config, contact.display, contact.uri) << endl; cout << endl; cout.flush(); } void t_userintf::cb_play_ringtone(int line) { if (!sys_config->get_play_ringtone()) return; if (tone_gen) { tone_gen->stop(); MEMMAN_DELETE(tone_gen); delete tone_gen; } // Determine ring tone string ringtone_file = phone->get_ringtone(line); tone_gen = new t_tone_gen(ringtone_file, sys_config->get_dev_ringtone()); MEMMAN_NEW(tone_gen); // If ring tone does not exist, then fall back to system default. if (!tone_gen->is_valid() && ringtone_file != FILE_RINGTONE) { MEMMAN_DELETE(tone_gen); delete tone_gen; tone_gen = new t_tone_gen(FILE_RINGTONE, sys_config->get_dev_ringtone()); MEMMAN_NEW(tone_gen); } // Play ring tone tone_gen->start_play_thread(true, INTERVAL_RINGTONE); } void t_userintf::cb_play_ringback(t_user *user_config) { if (!sys_config->get_play_ringback()) return; if (tone_gen) { tone_gen->stop(); MEMMAN_DELETE(tone_gen); delete tone_gen; } // Determine ring back tone string ringback_file; if (!user_config->get_ringback_file().empty()) { ringback_file = user_config->get_ringback_file(); } else if (!sys_config->get_ringback_file().empty()) { ringback_file = sys_config->get_ringback_file(); } else { // System default ringback_file = FILE_RINGBACK; } tone_gen = new t_tone_gen(ringback_file, sys_config->get_dev_speaker()); MEMMAN_NEW(tone_gen); // If ring back tone does not exist, then fall back to system default. if (!tone_gen->is_valid() && ringback_file != FILE_RINGBACK) { MEMMAN_DELETE(tone_gen); delete tone_gen; tone_gen = new t_tone_gen(FILE_RINGBACK, sys_config->get_dev_speaker()); MEMMAN_NEW(tone_gen); } // Play ring back tone tone_gen->start_play_thread(true, INTERVAL_RINGBACK); } void t_userintf::cb_stop_tone(int line) { // Only stop the tone if the current line is the active line if (line != phone->get_active_line()) return; if (!tone_gen) return; tone_gen->stop(); MEMMAN_DELETE(tone_gen); delete tone_gen; tone_gen = NULL; } void t_userintf::cb_notify_call(int line, string from_party) { // Play ringtone if the call is received on the active line if (line == phone->get_active_line() && !phone->is_line_auto_answered(line)) { cb_play_ringtone(line); } } void t_userintf::cb_stop_call_notification(int line) { cb_stop_tone(line); } void t_userintf::cb_dtmf_detected(int line, t_dtmf_ev dtmf_event) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": DTMF detected: "; if (is_valid_dtmf_ev(dtmf_event)) { cout << dtmf_ev2char(dtmf_event) << endl; } else { cout << "invalid DTMF telephone event (" << (int)dtmf_event << endl; } cout << endl; cout.flush(); } void t_userintf::cb_async_dtmf_detected(int line, t_dtmf_ev dtmf_event) { if (line >= NUM_USER_LINES) return; t_event_ui *event = new t_event_ui(TYPE_UI_CB_DTMF_DETECTED); MEMMAN_NEW(event); event->set_line(line); event->set_dtmf_event(dtmf_event); evq_ui_events.push(event); } void t_userintf::cb_send_dtmf(int line, t_dtmf_ev dtmf_event) { // No feed back in CLI } void t_userintf::cb_async_send_dtmf(int line, t_dtmf_ev dtmf_event) { t_event_ui *event = new t_event_ui(TYPE_UI_CB_SEND_DTMF); MEMMAN_NEW(event); event->set_line(line); event->set_dtmf_event(dtmf_event); evq_ui_events.push(event); } void t_userintf::cb_dtmf_not_supported(int line) { if (line >= NUM_USER_LINES) return; if (throttle_dtmf_not_supported) return; cout << endl; cout << "Line " << line + 1 << ": far end does not support DTMF events.\n"; cout << endl; cout.flush(); // Throttle subsequent call backs throttle_dtmf_not_supported = true; } void t_userintf::cb_dtmf_supported(int line) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": far end supports DTMF telephone event.\n"; cout << endl; cout.flush(); } void t_userintf::cb_line_state_changed(void) { // Nothing to do for CLI } void t_userintf::cb_async_line_state_changed(void) { t_event_ui *event = new t_event_ui(TYPE_UI_CB_LINE_STATE_CHANGED); MEMMAN_NEW(event); evq_ui_events.push(event); } void t_userintf::cb_send_codec_changed(int line, t_audio_codec codec) { // No feedback in CLI } void t_userintf::cb_recv_codec_changed(int line, t_audio_codec codec) { // No feedback in CLI } void t_userintf::cb_async_recv_codec_changed(int line, t_audio_codec codec) { t_event_ui *event = new t_event_ui(TYPE_UI_CB_RECV_CODEC_CHANGED); MEMMAN_NEW(event); event->set_line(line); event->set_codec(codec); evq_ui_events.push(event); } void t_userintf::cb_notify_recvd(int line, const t_request *r) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": received notification.\n"; cout << "Event: " << r->hdr_event.event_type << endl; cout << "State: " << r->hdr_subscription_state.substate << endl; if (r->hdr_subscription_state.substate == SUBSTATE_TERMINATED) { cout << "Reason: " << r->hdr_subscription_state.reason << endl; } t_response *sipfrag = (t_response *)((t_sip_body_sipfrag *)r->body)->sipfrag; cout << "Progress: " << sipfrag->code << ' ' << sipfrag->reason << endl; cout << endl; cout.flush(); } void t_userintf::cb_refer_failed(int line, const t_response *r) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": refer request failed.\n"; cout << r->code << ' ' << r->reason << endl; cout << endl; cout.flush(); } void t_userintf::cb_refer_result_success(int line) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": call successfully referred.\n"; cout << endl; cout.flush(); } void t_userintf::cb_refer_result_failed(int line) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": call refer failed.\n"; cout << endl; cout.flush(); } void t_userintf::cb_refer_result_inprog(int line) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": call refer in progress.\n"; cout << "No further notifications will be received.\n"; cout << endl; cout.flush(); } void t_userintf::cb_call_referred(t_user *user_config, int line, t_request *r) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": transferring call to "; cout << format_sip_address(user_config, r->hdr_refer_to.display, r->hdr_refer_to.uri); cout << endl; if (r->hdr_referred_by.is_populated()) { cout << "Transfer requested by "; cout << format_sip_address(user_config, r->hdr_referred_by.display, r->hdr_referred_by.uri); cout << endl; } cout << endl; cout.flush(); } void t_userintf::cb_retrieve_referrer(t_user *user_config, int line) { if (line >= NUM_USER_LINES) return; const t_call_info call_info = phone->get_call_info(line); cout << endl; cout << "Line " << line + 1 << ": call transfer failed.\n"; cout << "Retrieving call: \n"; cout << "From: "; cout << format_sip_address(user_config, call_info.from_display, call_info.from_uri); cout << endl; if (!call_info.from_organization.empty()) { cout << " " << call_info.from_organization; cout << endl; } cout << "To: "; cout << format_sip_address(user_config, call_info.to_display, call_info.to_uri); cout << endl; if (!call_info.to_organization.empty()) { cout << " " << call_info.to_organization; cout << endl; } cout << "Subject: "; cout << call_info.subject; cout << endl << endl; cout.flush(); } void t_userintf::cb_consultation_call_setup(t_user *user_config, int line) { if (line >= NUM_USER_LINES) return; const t_call_info call_info = phone->get_call_info(line); cout << endl; cout << "Line " << line + 1 << ": setup consultation call.\n"; cout << "From: "; cout << format_sip_address(user_config, call_info.from_display, call_info.from_uri); cout << endl; if (!call_info.from_organization.empty()) { cout << " " << call_info.from_organization; cout << endl; } cout << "To: "; cout << format_sip_address(user_config, call_info.to_display, call_info.to_uri); cout << endl; if (!call_info.to_organization.empty()) { cout << " " << call_info.to_organization; cout << endl; } cout << "Subject: "; cout << call_info.subject; cout << endl << endl; cout.flush(); } void t_userintf::cb_stun_failed(t_user *user_config, int err_code, const string &err_reason) { cout << endl; cout << user_config->get_profile_name(); cout << ", STUN request failed: "; cout << err_code << " " << err_reason << endl; cout << endl; cout.flush(); } void t_userintf::cb_stun_failed(t_user *user_config) { cout << endl; cout << user_config->get_profile_name(); cout << ", STUN request failed.\n"; cout << endl; cout.flush(); } bool t_userintf::cb_ask_user_to_redirect_invite(t_user *user_config, const t_url &destination, const string &display) { // Cannot ask user for permission in CLI, so deny redirection. return false; } bool t_userintf::cb_ask_user_to_redirect_request(t_user *user_config, const t_url &destination, const string &display, t_method method) { // Cannot ask user for permission in CLI, so deny redirection. return false; } bool t_userintf::cb_ask_credentials(t_user *user_config, const string &realm, string &username, string &password) { // Cannot ask user for username/password in CLI return false; } void t_userintf::cb_ask_user_to_refer(t_user *user_config, const t_url &refer_to_uri, const string &refer_to_display, const t_url &referred_by_uri, const string &referred_by_display) { // Cannot ask user for permission in CLI, so deny REFER send_refer_permission(false); } void t_userintf::send_refer_permission(bool permission) { evq_trans_layer->push_refer_permission_response(permission); } void t_userintf::cb_show_msg(const string &msg, t_msg_priority prio) { cout << endl; switch (prio) { case MSG_NO_PRIO: break; case MSG_INFO: cout << "Info: "; break; case MSG_WARNING: cout << "Warning: "; break; case MSG_CRITICAL: cout << "Critical: "; break; default: cout << "???: "; } cout << msg << endl; cout << endl; cout.flush(); } bool t_userintf::cb_ask_msg(const string &msg, t_msg_priority prio) { // Cannot ask questions in CLI mode. // Print message and return false cb_show_msg(msg, prio); return false; } void t_userintf::cb_display_msg(const string &msg, t_msg_priority prio) { // In CLI mode this is the same as cb_show_msg cb_show_msg(msg, prio); } void t_userintf::cb_async_display_msg(const string &msg, t_msg_priority prio) { t_event_ui *event = new t_event_ui(TYPE_UI_CB_DISPLAY_MSG); MEMMAN_NEW(event); event->set_display_msg(msg, prio); evq_ui_events.push(event); } void t_userintf::cb_log_updated(bool log_zapped) { // In CLI mode there is no log viewer. } void t_userintf::cb_call_history_updated(void) { // In CLI mode there is no call history viewer. } void t_userintf::cb_missed_call(int num_missed_calls) { // In CLI mode there is no missed call indication. } void t_userintf::cb_nat_discovery_progress_start(int num_steps) { cout << endl; cout << "Firewall/NAT discovery in progress.\n"; cout << "Please wait.\n"; cout << endl; } void t_userintf::cb_nat_discovery_finished(void) { // Nothing to do in CLI mode. } void t_userintf::cb_nat_discovery_progress_step(int step) { // Nothing to do in CLI mode. } bool t_userintf::cb_nat_discovery_cancelled(void) { // User cannot cancel NAT discovery in CLI mode. return false; } void t_userintf::cb_line_encrypted(int line, bool encrypted, const string &cipher_mode) { if (line >= NUM_USER_LINES) return; cout << endl; if (encrypted) { cout << "Line " << line + 1 << ": audio encryption enabled ("; cout << cipher_mode << ").\n"; } else { cout << "Line " << line + 1 << ": audio encryption disabled.\n"; } cout << endl; cout.flush(); } void t_userintf::cb_async_line_encrypted(int line, bool encrypted, const string &cipher_mode) { t_event_ui *event = new t_event_ui(TYPE_UI_CB_LINE_ENCRYPTED); MEMMAN_NEW(event); event->set_line(line); event->set_encrypted(encrypted); event->set_cipher_mode(cipher_mode); evq_ui_events.push(event); } void t_userintf::cb_show_zrtp_sas(int line, const string &sas) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": ZRTP SAS = " << sas << endl; cout << "Confirm the SAS if it is correct.\n"; cout << endl; cout.flush(); } void t_userintf::cb_async_show_zrtp_sas(int line, const string &sas) { t_event_ui *event = new t_event_ui(TYPE_UI_CB_SHOW_ZRTP_SAS); MEMMAN_NEW(event); event->set_line(line); event->set_zrtp_sas(sas); evq_ui_events.push(event); } void t_userintf::cb_zrtp_confirm_go_clear(int line) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": remote user disabled encryption.\n"; cout << endl; cout.flush(); phone->pub_zrtp_go_clear_ok(line); } void t_userintf::cb_async_zrtp_confirm_go_clear(int line) { t_event_ui *event = new t_event_ui(TYPE_UI_CB_ZRTP_CONFIRM_GO_CLEAR); MEMMAN_NEW(event); event->set_line(line); evq_ui_events.push(event); } void t_userintf::cb_zrtp_sas_confirmed(int line) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": SAS confirmed.\n"; cout << endl; cout.flush(); } void t_userintf::cb_zrtp_sas_confirmation_reset(int line) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": SAS confirmation reset.\n"; cout << endl; cout.flush(); } void t_userintf::cb_update_mwi(void) { // Nothing to do in CLI mode. } void t_userintf::cb_mwi_subscribe_failed(t_user *user_config, t_response *r, bool first_failure) { // Only report the first failure in a sequence of failures if (!first_failure) return; cout << endl; cout << user_config->get_profile_name(); cout << ", MWI subscription failed: " << r->code << ' ' << r->reason << endl; cout << endl; cout.flush(); } void t_userintf::cb_mwi_terminated(t_user *user_config, const string &reason) { cout << endl; cout << user_config->get_profile_name(); cout << ", MWI subscription terminated: " << reason << endl; cout << endl; cout.flush(); } bool t_userintf::cb_message_request(t_user *user_config, t_request *r) { cout << endl; cout << "Received message\n"; cout << "From:\t\t"; string from_party = format_sip_address(user_config, r->hdr_from.get_display_presentation(), r->hdr_from.uri); cout << from_party << endl; if (r->hdr_organization.is_populated()) { cout << "Organization:\t" << r->hdr_organization.name << endl; } cout << "To:\t\t"; cout << format_sip_address(user_config, r->hdr_to.display, r->hdr_to.uri) << endl; if (r->hdr_subject.is_populated()) { cout << "Subject:\t" << r->hdr_subject.subject << endl; } cout << endl; if (r->body && r->body->get_type() == BODY_PLAIN_TEXT) { t_sip_body_plain_text *sb = dynamic_cast(r->body); cout << sb->text << endl; } else if (r->body && r->body->get_type() == BODY_HTML_TEXT) { t_sip_body_html_text *sb = dynamic_cast(r->body); cout << sb->text << endl; } else { cout << "Unsupported content type.\n"; } cout << endl; cout.flush(); // There are no session in CLI mode, so all messages are accepted. return true; } void t_userintf::cb_message_response(t_user *user_config, t_response *r, t_request *req) { if (r->is_success()) return; cout << endl; cout << "Failed to send MESSAGE.\n"; cout << r->code << " " << r->reason << endl; cout << endl; cout.flush(); } void t_userintf::cb_im_iscomposing_request(t_user *user_config, t_request *r, im::t_composing_state state, time_t refresh) { // Nothing to do in CLI mode return; } void t_userintf::cb_im_iscomposing_not_supported(t_user *user_config, t_response *r) { // Nothing to do in CLI mode return; } bool t_userintf::get_last_call_info(t_url &url, string &display, string &subject, t_user **user_config, bool &hide_user) const { if (!last_called_url.is_valid()) return false; url = last_called_url; display = last_called_display; subject = last_called_subject; *user_config = phone->ref_user_profile(last_called_profile); hide_user = last_called_hide_user; return *user_config != NULL; } bool t_userintf::can_redial(void) const { return last_called_url.is_valid() && phone->ref_user_profile(last_called_profile) != NULL; } void t_userintf::cmd_call(const string &destination, bool immediate) { string s = "invite "; s += destination; exec_command(s); } void t_userintf::cmd_quit(void) { exec_command("quit"); } void t_userintf::cmd_quit_async(void) { t_event_ui *event = new t_event_ui(TYPE_UI_CB_QUIT); MEMMAN_NEW(event); evq_ui_events.push(event); } void t_userintf::cmd_cli(const string &command, bool immediate) { exec_command(command, immediate); } void t_userintf::cmd_show(void) { // Do nothing in CLI mode. } void t_userintf::cmd_hide(void) { // Do nothing in CLI mode. } string t_userintf::get_name_from_abook(t_user *user_config, const t_url &u) { return ab_local->find_name(user_config, u); } void *process_events_main(void *arg) { ui->process_events(); return NULL; } const list& t_userintf::get_all_commands(void) { return all_commands; } twinkle-1.10.1/src/userintf.h000066400000000000000000000455411277565361200160750ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _USERINTF_H #define _USERINTF_H #include #include #include "events.h" #include "phone.h" #include "protocol.h" #include "parser/request.h" #include "parser/response.h" #include "audio/tone_gen.h" #include "threads/thread.h" #include "im/msg_session.h" #include "presence/presence_state.h" #include "twinkle_config.h" #define PRODUCT_DATE VERSION_DATE #define PRODUCT_AUTHOR "Michel de Boer and contributors" // Tone definitions // The intervals indicate the length of silence between repetitions // of the wav file. Duration is in ms #define FILE_RINGTONE "ringtone.wav" #define INTERVAL_RINGTONE 3000 #define FILE_RINGBACK "ringback.wav" #define INTERVAL_RINGBACK 3000 using namespace std; struct t_command_arg { char flag; string value; }; class t_userintf : public i_prohibit_thread { protected: enum t_zrtp_cmd { ZRTP_ENCRYPT, ZRTP_GO_CLEAR, ZRTP_CONFIRM_SAS, ZRTP_RESET_SAS }; private: bool end_interface; // indicates if interface loop should quit list all_commands; // list of all commands t_tone_gen *tone_gen; // tone generator for ringing // The user for which out-of-dialog requests are executed. t_user *active_user; // The user can type a prefix of the command only. This method // completes a prefix to a full command. // If no command is found then the empty string is returned. // If the prefix is ambiguous, then argument ambiguous is set to true // and the empty string is returned. string complete_command(const string &c, bool &ambiguous); // Parse command arguments. The list must contain the command as first // element followed by the arguments. bool parse_args(const list command_list, list &al); // The command_list must contain the command itself as first // argument. Subsequent elements are the arguments. bool exec_invite(const list command_list, bool immediate = false); bool exec_redial(const list command_list); bool exec_answer(const list command_list); bool exec_answerbye(const list command_list); bool exec_reject(const list command_list); bool exec_redirect(const list command_list, bool immediate = false); bool exec_dnd(const list command_list); bool exec_auto_answer(const list command_list); bool exec_bye(const list command_list); bool exec_hold(const list command_list); bool exec_retrieve(const list command_list); bool exec_refer(const list command_list, bool immediate = false); bool exec_conference(const list command_list); bool exec_mute(const list command_list); bool exec_dtmf(const list command_list); bool exec_register(const list command_list); bool exec_deregister(const list command_list); bool exec_fetch_registrations(const list command_list); bool exec_options(const list command_list, bool immediate = false); bool exec_line(const list command_list); bool exec_user(const list command_list); bool exec_zrtp(const list command_list); bool exec_message(const list command_list); bool exec_presence(const list command_list); bool exec_quit(const list command_list); bool exec_help(const list command_list); protected: t_phone *phone; // Asynchronous event queue t_event_queue evq_ui_events; t_thread *thr_process_events; // Indicates if commands should print output to stdout bool use_stdout; // Throttle dtmtf not supported messages bool throttle_dtmf_not_supported; // Last call information t_url last_called_url; string last_called_display; string last_called_subject; string last_called_profile; // profile used to make the call bool last_called_hide_user; // The do_* methods perform the commands parsed by the exec_* methods. virtual bool do_invite(const string &destination, const string &display, const string &subject, bool immediate, bool anonymous); virtual void do_redial(void); virtual void do_answer(void); virtual void do_answerbye(void); virtual void do_reject(void); virtual void do_redirect(bool show_status, bool type_present, t_cf_type cf_type, bool action_present, bool enable, int num_redirections, const list &dest_strlist, bool immediate); virtual void do_dnd(bool show_status, bool toggle, bool enable); virtual void do_auto_answer(bool show_status, bool toggle, bool enable); virtual void do_bye(void); virtual void do_hold(void); virtual void do_retrieve(void); virtual bool do_refer(const string &destination, t_transfer_type transfer_type, bool immediate); virtual void do_conference(void); virtual void do_mute(bool show_status, bool toggle, bool enable); virtual void do_dtmf(const string &digits); virtual void do_register(bool reg_all_profiles); virtual void do_deregister(bool dereg_all_profiles, bool dereg_all_devices); virtual void do_fetch_registrations(void); virtual bool do_options(bool dest_set, const string &destination, bool immediate); virtual void do_line(int line); virtual void do_user(const string &profile_name); virtual void do_zrtp(t_zrtp_cmd zrtp_cmd); virtual bool do_message(const string &destination, const string &display, const im::t_msg &msg); virtual void do_presence(t_presence_state::t_basic_state basic_state); virtual void do_quit(void); virtual void do_help(const list &al); public: t_userintf(t_phone *_phone); virtual ~t_userintf(); /** * Expand a SIP destination to a full SIP/TEL uri, i.e. add sip/tel scheme * and domain if these are missing. * @param user_config [in] User profile of the user for which the expansion is done. * @param dst [in] The address string to expand. * @param scheme [in] Scheme to expand to (sip/tel/""). If scheme is empty then * the expansion is done according to preferences from the user profile. * @return The expanded address. */ string expand_destination(t_user *user_config, const string &dst, const string &scheme = ""); // Expand a SIP destination into a display and a full SIP uri void expand_destination(t_user *user_config, const string &dst, string &display, string &dst_url); void expand_destination(t_user *user_config, const string &dst, t_display_url &display_url); // Expand a SIP destination as above, but split off any headers if any. // If the subject header is present, then its value will be returned in // subject. // The dst_no_headers parameter will contain the dst string with the headers // cut off. void expand_destination(t_user *user_config, const string &dst, t_display_url &display_url, string &subject, string &dst_no_headers); // Format a SIP address for user display virtual string format_sip_address(t_user *user_config, const string &display, const t_url &uri) const; // Format a warning for user display virtual list format_warnings(const t_hdr_warning &hdr_warning) const; // Format a codec for user display virtual string format_codec(t_audio_codec codec) const; // The immediate flag is by the cmd_cli method (see below) bool exec_command(const string &command_line, bool immediate = false); // Run the user interface virtual void run(void); // This method executes asynchronous uier interface events virtual void process_events(void); // Save user interface state to system settings virtual void save_state(void); // Restore user interface state from system settings virtual void restore_state(void); // Lock the user interface to synchornize output virtual void lock(void); virtual void unlock(void); // Select a network interface. Returns string representation of IP address. virtual string select_network_intf(void); // Select a user configuration file. Returns false if selection failed. virtual bool select_user_config(list &config_files); // Call back functions virtual void cb_incoming_call(t_user *user_config, int line, const t_request *r); virtual void cb_call_cancelled(int line); virtual void cb_far_end_hung_up(int line); virtual void cb_answer_timeout(int line); virtual void cb_sdp_answer_not_supported(int line, const string &reason); virtual void cb_sdp_answer_missing(int line); virtual void cb_unsupported_content_type(int line, const t_sip_message *r); virtual void cb_ack_timeout(int line); virtual void cb_100rel_timeout(int line); virtual void cb_prack_failed(int line, const t_response *r); virtual void cb_provisional_resp_invite(int line, const t_response *r); virtual void cb_cancel_failed(int line, const t_response *r); virtual void cb_call_answered(t_user *user_config, int line, const t_response *r); virtual void cb_call_failed(t_user *user_config, int line, const t_response *r); virtual void cb_stun_failed_call_ended(int line); virtual void cb_call_ended(int line); virtual void cb_call_established(int line); virtual void cb_options_response(const t_response *r); virtual void cb_reinvite_success(int line, const t_response *r); virtual void cb_reinvite_failed(int line, const t_response *r); virtual void cb_retrieve_failed(int line, const t_response *r); virtual void cb_invalid_reg_resp(t_user *user_config, const t_response *r, const string &reason); virtual void cb_register_success(t_user *user_config, const t_response *r, unsigned long expires, bool first_success); virtual void cb_register_failed(t_user *user_config, const t_response *r, bool first_failure); virtual void cb_register_stun_failed(t_user *user_config, bool first_failure); virtual void cb_deregister_success(t_user *user_config, const t_response *r); virtual void cb_deregister_failed(t_user *user_config, const t_response *r); virtual void cb_fetch_reg_failed(t_user *user_config, const t_response *r); virtual void cb_fetch_reg_result(t_user *user_config, const t_response *r); virtual void cb_register_inprog(t_user *user_config, t_register_type register_type); virtual void cb_redirecting_request(t_user *user_config, int line, const t_contact_param &contact); virtual void cb_redirecting_request(t_user *user_config, const t_contact_param &contact); virtual void cb_play_ringtone(int line); virtual void cb_play_ringback(t_user *user_config); virtual void cb_stop_tone(int line); virtual void cb_notify_call(int line, string from_party); virtual void cb_stop_call_notification(int line); virtual void cb_dtmf_detected(int line, t_dtmf_ev dtmf_event); virtual void cb_async_dtmf_detected(int line, t_dtmf_ev dtmf_event); virtual void cb_send_dtmf(int line, t_dtmf_ev dtmf_event); virtual void cb_async_send_dtmf(int line, t_dtmf_ev dtmf_event); virtual void cb_dtmf_not_supported(int line); virtual void cb_dtmf_supported(int line); virtual void cb_line_state_changed(void); virtual void cb_async_line_state_changed(void); virtual void cb_send_codec_changed(int line, t_audio_codec codec); virtual void cb_recv_codec_changed(int line, t_audio_codec codec); virtual void cb_async_recv_codec_changed(int line, t_audio_codec codec); virtual void cb_notify_recvd(int line, const t_request *r); virtual void cb_refer_failed(int line, const t_response *r); virtual void cb_refer_result_success(int line); virtual void cb_refer_result_failed(int line); virtual void cb_refer_result_inprog(int line); // A call is being referred by the far end. r must be the REFER request. virtual void cb_call_referred(t_user *user_config, int line, t_request *r); // The reference failed. Call to referrer is retrieved. virtual void cb_retrieve_referrer(t_user *user_config, int line); // A consulation call for a call transfer is being setup. virtual void cb_consultation_call_setup(t_user *user_config, int line); // STUN errors virtual void cb_stun_failed(t_user *user_config, int err_code, const string &err_reason); virtual void cb_stun_failed(t_user *user_config); // Interactive call back functions virtual bool cb_ask_user_to_redirect_invite(t_user *user_config, const t_url &destination, const string &display); virtual bool cb_ask_user_to_redirect_request(t_user *user_config, const t_url &destination, const string &display, t_method method); virtual bool cb_ask_credentials(t_user *user_config, const string &realm, string &username, string &password); // Ask questions asynchronously. virtual void cb_ask_user_to_refer(t_user *user_config, const t_url &refer_to_uri, const string &refer_to_display, const t_url &referred_by_uri, const string &referred_by_display); // Send the answer for refer permission to the transaction layer. void send_refer_permission(bool permission); // Show an error message to the user. Depending on the interface mode // the user has to acknowledge the error before processing continues. virtual void cb_show_msg(const string &msg, t_msg_priority prio = MSG_INFO); // Ask a yes/no question to the user. // Returns true for yes and false for no. virtual bool cb_ask_msg(const string &msg, t_msg_priority prio = MSG_INFO); /** * Display an error/information message. * @param msg [in] Message to display. * @param prio [in] Priority associated with the message. */ virtual void cb_display_msg(const string &msg, t_msg_priority prio = MSG_INFO); /** * Display an error/information message in an asynchronous way. * @param msg [in] Message to display. * @param prio [in] Priority associated with the message. */ virtual void cb_async_display_msg(const string &msg, t_msg_priority prio = MSG_INFO); // Log file has been updated virtual void cb_log_updated(bool log_zapped = false); // Call history has been updated virtual void cb_call_history_updated(void); virtual void cb_missed_call(int num_missed_calls); // Show firewall/NAT discovery progress virtual void cb_nat_discovery_progress_start(int num_steps); virtual void cb_nat_discovery_progress_step(int step); virtual void cb_nat_discovery_finished(void); virtual bool cb_nat_discovery_cancelled(void); // ZRTP virtual void cb_line_encrypted(int line, bool encrypted, const string &cipher_mode = ""); virtual void cb_async_line_encrypted(int line, bool encrypted, const string &cipher_mode = ""); virtual void cb_show_zrtp_sas(int line, const string &sas); virtual void cb_async_show_zrtp_sas(int line, const string &sas); virtual void cb_zrtp_confirm_go_clear(int line); virtual void cb_async_zrtp_confirm_go_clear(int line); virtual void cb_zrtp_sas_confirmed(int line); virtual void cb_zrtp_sas_confirmation_reset(int line); // MWI virtual void cb_update_mwi(void); virtual void cb_mwi_subscribe_failed(t_user *user_config, t_response *r, bool first_failure); virtual void cb_mwi_terminated(t_user *user_config, const string &reason); /** @name Instant messaging */ //@{ /** * Incoming MESSAGE request callback. * @param user_config [in] User profile of the user receiving this MESSAGE request. * @param r [in] The MESSAGE request. * @return True if the message is accepted. * @return False if the message is rejected, i.e. maximum number of sessions reached. */ virtual bool cb_message_request(t_user *user_config, t_request *r); /** * Incoming MESSAGE response callback. * @param user_config [in] User profile of the user receiving this MESSAGE response. * @param r [in] The MESSAGE response. * @param req [in] The MESSAGE request for which the response is received. */ virtual void cb_message_response(t_user *user_config, t_response *r, t_request *req); /** * Incoming MESSAGE request with composing indication callback. * @param user_config [in] User profile of the user receiving this MESSAGE response. * @param r [in] The MESSAGE request containing the composing indication. * @param state [in] The message composing state. * @param refresh [in] The refresh interval in seconds when state is active. */ virtual void cb_im_iscomposing_request(t_user *user_config, t_request *r, im::t_composing_state state, time_t refresh); /** * Indication that the far-end does not support message composing indications. * @param user_config [in] User profile of the user receiving this MESSAGE response. * @param r [in] The MESSAGE response on the composing indication. */ virtual void cb_im_iscomposing_not_supported(t_user *user_config, t_response *r); //@} // Get last call information // Returns true if last call information is valid // Returns false is there is no valid last call information virtual bool get_last_call_info(t_url &url, string &display, string &subject, t_user **user_config, bool &hide_user) const; virtual bool can_redial(void) const; // Execute external commands // Some comments require confirmation from the user via the user // interface, e.g. in GUI mode, a call dialog may popup for cmd_call. // The 'immediate' flag indicates that no user confirmation is required. // The command should be executed immediately. virtual void cmd_call(const string &destination, bool immediate); virtual void cmd_quit(void); void cmd_quit_async(void); virtual void cmd_cli(const string &command, bool immeidate); /** Execute the SHOW command. */ virtual void cmd_show(void); /** Execute the HIDE command. */ virtual void cmd_hide(void); // Lookup a URL in the address book virtual string get_name_from_abook(t_user *user_config, const t_url &u); // Get all command names const list& get_all_commands(void); // Asynchronously run a function on this class' event queue void run_on_event_queue(std::function fn); }; void *process_events_main(void *arg); extern t_userintf *ui; #endif twinkle-1.10.1/src/util.cpp000066400000000000000000000345541277565361200155500ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "twinkle_config.h" string month_abbrv[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; string month_full[] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; string day_abbrv[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; string random_token(int length) { string s; for (int i = 0; i < length; i++) { s += char(rand() % 26 + 97); } return s; } string random_hexstr(int length) { string s; int x; for (int i = 0; i < length; i++) { x = rand() % 16; if (x <= 9) s += '0' + x; else s += 'a' + x - 10; } return s; } string float2str(float f, int precision) { ostringstream s; // Force the locale to POSIX, such that a dot is used for // the decimal point. s.imbue(locale("POSIX")); s.setf(ios::fixed,ios::floatfield); s.precision(precision); s << f; return s.str(); } string int2str(int i, const char *format) { char buf[32]; snprintf(buf, 32, format, i); return string(buf); } string int2str(int i) { return int2str(i, "%d"); } string ulong2str(unsigned long i, const char *format) { char buf[32]; snprintf(buf, 32, format, i); return string(buf); } string ulong2str(unsigned long i) { return ulong2str(i, "%u"); } string ptr2str(void *p) { char buf[32]; snprintf(buf, 32, "%p", p); return string(buf); } string bool2str(bool b) { return (b ? "true" : "false"); } string time2str(time_t t, const char *format) { struct tm tm; char buf[64]; localtime_r(&t, &tm); strftime(buf, 64, format, &tm); return string(buf); } string current_time2str(const char *format) { struct timeval t; gettimeofday(&t, NULL); return time2str(t.tv_sec, format); } string weekday2str(int wkday) { if (wkday >= 0 && wkday <= 6) return day_abbrv[wkday]; return "XXX"; } string month2str(int month) { if (month >= 0 && month <= 11) return month_abbrv[month]; return "XXX"; } int str2month_full(const string &month) { for (int i = 0; i < 12; i++) { if (cmp_nocase(month_full[i], month) == 0) { return i; } } return 0; } string duration2str(unsigned long seconds) { string result; long remainder, h, m, s; h = seconds / 3600; remainder = seconds % 3600; m = remainder / 60; s = remainder % 60; if (h > 0) { result = ulong2str(h); result += "h "; } if (!result.empty() || m > 0) { result += ulong2str(m); result += "m "; } result += ulong2str(s); result += "s"; return result; } string timer2str(unsigned long seconds) { string result; unsigned long remainder, h, m, s; h = seconds / 3600; remainder = seconds % 3600; m = remainder / 60; s = remainder % 60; char buf[16]; snprintf(buf, 16, "%01lu:%02lu:%02lu", h, m, s); return string(buf); } static uint8 hexdig2value(char hexdig) { uint8 val = 0; if (hexdig >= '0' && hexdig <= '9') val = hexdig - '0'; else if (hexdig >= 'a' && hexdig <= 'f') val = hexdig - 'a' + 10; else if (hexdig >= 'A' && hexdig <= 'F') val = hexdig - 'A' + 10; return val; } static char value2hexdig(uint8 val) { char c = '0'; if (val <= 9) { c = '0' + val; } else if (val <= 15) { c = 'a' + val - 10; } return c; } unsigned long hex2int(const string &h) { unsigned long u = 0; int power = 1; for (string::const_reverse_iterator i = h.rbegin(); i != h.rend(); ++i) { u += hexdig2value(*i) * power; power = power * 16; } return u; } void hex2binary(const string &h, uint8 *buf) { uint8 *p = buf; bool hi_nibble = true; for (string::const_iterator i = h.begin() ; i != h.end(); ++i) { if (hi_nibble) { *p = hexdig2value(*i) << 4; } else { *(p++) |= hexdig2value(*i); } hi_nibble = !hi_nibble; } } string binary2hex(uint8 *buf, unsigned long len) { string s; for (uint8 *p = buf; p < buf + len; ++p) { s += value2hexdig((*p >> 4) & 0xf); s += value2hexdig(*p & 0xf); } return s; } string tolower(const string &s) { string result; for (string::const_iterator i = s.begin(); i != s.end(); ++i) { result += tolower(*i); } return result; } string toupper(const string &s) { string result; for (string::const_iterator i = s.begin(); i != s.end(); ++i) { result += toupper(*i); } return result; } string rtrim(const string &s) { string::size_type i; i = s.find_last_not_of(' '); if (i == string::npos) return ""; if (i == s.size()-1) return s; return s.substr(0, i+1); } string ltrim(const string &s) { string::size_type i; i = s.find_first_not_of(' '); if (i == string::npos) return ""; if (i == 0) return s; return s.substr(i, s.size()-i+1); } string trim(const string &s) { return ltrim(rtrim(s)); } string padleft(const string &s, char c, unsigned long len) { string result(c, len); result += s; return result.substr(result.size() - len); } int cmp_nocase(const string &s1, const string &s2) { string::const_iterator i1 = s1.begin(); string::const_iterator i2 = s2.begin(); while (i1 != s1.end() && i2 != s2.end()) { if (toupper(*i1) != toupper(*i2)) { return (toupper(*i1) < toupper(*i2)) ? -1 : 1; } ++i1; ++i2; } if (s1.size() == s2.size()) return 0; if (s1.size() < s2.size()) return -1; return 1; } bool must_quote(const string &s) { string special("()<>@,;:\\\"/[]?={} \t"); if (s.size() == 0) return true; return (s.find_first_of(special) != string::npos); } string escape(const string &s, char c) { string result; for (string::size_type i = 0; i < s.size(); i++) { if (s[i] == '\\' || s[i] == c) { result += '\\'; } result += s[i]; } return result; } string unescape(const string &s) { string result; for (string::size_type i = 0; i < s.size(); i++) { if (s[i] == '\\' && i < s.size() - 1) { i++; } result += s[i]; } return result; } string escape_hex(const string &s, const string &unreserved) { string result; for (string::size_type i = 0; i < s.size(); i++) { if (unreserved.find(s[i], 0) != string::npos) { // Unreserved symbol result += s[i]; } else { // Reserved symbol result += int2str((int)s[i], "%%%02x"); } } return result; } string unescape_hex(const string &s) { string result; for (string::size_type i = 0; i < s.size(); i++) { if (s[i] == '%' && i < s.size() - 2 && isxdigit(s[i+1]) && isxdigit(s[i+2])) { // Escaped hex-value string hexval = s.substr(i+1, 2); result += static_cast(hex2int(hexval)); i += 2; } else { result += s[i]; } } return result; } string replace_char(const string &s, char from, char to) { string result = s; for (string::size_type i = 0; i < result.size(); i++) { if (result[i] == from) result[i] = to; } return result; } string replace_first(const string &s, const string &from, const string &to) { string result = s; string::size_type i = result.find(from, 0); if (i != string::npos) { result.replace(i, from.size(), to); } return result; } vector split(const string &s, char c) { string::size_type i; string::size_type j = 0; vector l; while (true) { i = s.find(c, j); if (i == string::npos) { l.push_back(s.substr(j)); return l; } if (i == j) l.push_back(""); else l.push_back(s.substr(j, i-j)); j = i+1; if (j == s.size()) { l.push_back(""); return l; } } } vector split(const string &s, const string& separator) { string::size_type i; string::size_type j = 0; vector l; while (true) { i = s.find(separator, j); if (i == string::npos) { l.push_back(s.substr(j)); return l; } if (i == j) l.push_back(""); else l.push_back(s.substr(j, i-j)); j = i + separator.size(); if (j == s.size()) { l.push_back(""); return l; } } } vector split_linebreak(const string &s) { if (s.find("\r\n") != string::npos) { return split(s, "\r\n"); } else if (s.find("\r") != string::npos) { return split(s, "\r"); } return split(s, "\n"); } vector split_on_first(const string &s, char c) { vector l; string::size_type i = s.find(c); if (i == string::npos) { l.push_back(s); } else { if (i == 0) { l.push_back(""); } else { l.push_back(s.substr(0, i)); } if (i == s.size() - 1) { l.push_back(""); } else { l.push_back(s.substr(i + 1)); } } return l; } vector split_on_last(const string &s, char c) { vector l; string::size_type i = s.find_last_of(c); if (i == string::npos) { l.push_back(s); } else { if (i == 0) { l.push_back(""); } else { l.push_back(s.substr(0, i)); } if (i == s.size() - 1) { l.push_back(""); } else { l.push_back(s.substr(i + 1)); } } return l; } vector split_escaped(const string &s, char c) { vector l; string::size_type start_pos = 0; for (string::size_type i = 0; i < s.size(); i++) { if (s[i] == '\\') { // Skip escaped character if (i < s.size()) i++; continue; } if (s[i] == c) { l.push_back(unescape(s.substr(start_pos, i - start_pos))); start_pos = i + 1; } } if (start_pos < s.size()) { l.push_back(unescape(s.substr(start_pos, s.size() - start_pos))); } else if (start_pos == s.size()) { l.push_back(""); } return l; } vector split_ws(const string &s, bool quote_sensitive) { vector l; bool in_quotes = false; string::size_type start_pos = 0; for (string::size_type i = 0; i < s.size(); i++ ) { if (quote_sensitive && s[i] == '"') { in_quotes = !in_quotes; continue; } if (in_quotes) continue; if (s[i] == ' ' || s[i] == '\t') { // Skip consecutive white space if (start_pos != i) { l.push_back(s.substr(start_pos, i - start_pos)); } start_pos = i + 1; } } if (start_pos < s.size()) { l.push_back(s.substr(start_pos, s.size() - start_pos)); } return l; } string join_strings(const vector &v, const string &separator) { string text; for (vector::const_iterator it = v.begin(); it != v.end(); ++it) { if (it != v.begin()) { text += separator; } text += *it; } return text; } string unquote(const string &s) { if (s.size() <= 1) return s; if (s[0] == '"' && s[s.size() - 1] == '"') return s.substr(1, s.size() - 2); return s; } bool is_number(const string &s) { if (s.empty()) return false; for (string::size_type i = 0; i < s.size(); i++ ) { if (!isdigit(s[i])) return false; } return true; } bool is_ipaddr(const string &s) { vector l = split(s, '.'); if (l.size() != 4) return false; for (vector::iterator i = l.begin(); i != l.end(); ++i) { if (!is_number(*i) || atoi(i->c_str()) > 255) return false; } return true; } bool yesno2bool(const string &yesno) { return (yesno == "yes" ? true : false); } string bool2yesno(bool b) { return (b ? "yes" : "no"); } string str2dtmf(const string &s) { string result; string to_convert = tolower(s); for (string::size_type i = 0; i < to_convert.size(); i++) { switch (to_convert[i]) { case '1': result += '1'; break; case '2': case 'a': case 'b': case 'c': result += '2'; break; case '3': case 'd': case 'e': case 'f': result += '3'; break; case '4': case 'g': case 'h': case 'i': result += '4'; break; case '5': case 'j': case 'k': case 'l': result += '5'; break; case '6': case 'm': case 'n': case 'o': result += '6'; break; case '7': case 'p': case 'q': case 'r': case 's': result += '7'; break; case '8': case 't': case 'u': case 'v': result += '8'; break; case '9': case 'w': case 'x': case 'y': case 'z': result += '9'; break; case '0': case ' ': result += '0'; break; case '#': case '*': result += to_convert[i]; break; } } return result; } bool looks_like_phone(const string &s, const string &special_symbols) { string phone_symbols= special_symbols + "0123456789*#+ \t"; string t; for (string::const_iterator i = s.begin(); i != s.end(); ++i) { if (phone_symbols.find(*i) == string::npos) return false; } return true; } string remove_symbols(const string &s, const string &special_symbols) { string result; for (string::const_iterator i = s.begin(); i != s.end(); ++i) { if (special_symbols.find(*i) == string::npos) { result += *i; } } return result; } string remove_white_space(const string &s) { string result; for (string::const_iterator i = s.begin(); i != s.end(); ++i) { if (*i != ' ' && *i != '\t') { result += *i; } } return result; } string dotted_truncate(const string &s, string::size_type len) { if (len >= s.size()) return s; return s.substr(0, len) + "..."; } string to_printable(const string &s) { string result; for (string::const_iterator i = s.begin(); i != s.end(); ++i) { if (isprint(*i) || *i == '\n' || *i == '\r') { result += *i; } else { result += '.'; } } return result; } string get_error_str(int errnum) { #if HAVE_STRERROR_R char buf[81]; memset(buf, 0, sizeof(buf)); #if STRERROR_R_CHAR_P string errmsg(strerror_r(errnum, buf, sizeof(buf)-1)); #else string errmsg; if (strerror_r(errnum, buf, sizeof(buf)-1) == 0) { errmsg = buf; } else { errmsg = "unknown error: "; errmsg += int2str(errnum); } #endif #else string errmsg("strerror_r is not available: "); errmsg += int2str(errnum); #endif return errmsg; } twinkle-1.10.1/src/util.h000066400000000000000000000206501277565361200152050ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #ifndef _UTIL_H #define _UTIL_H /** * @file * Utility functions */ #include #include #include using namespace std; string random_token(int length); string random_hexstr(int length); /** * Convert a float to a string. * @param f [in] Float to convert. * @param precision [in] Number of digits after the decimal point in output. * @return String representation of the float. */ string float2str(float f, int precision); // Convert an int to a string. format is a printf format string int2str(int i, const char *format); string int2str(int i); // Convert a ulong to a string. format is a printf format string ulong2str(unsigned long i, const char *format); string ulong2str(unsigned long i); // Convert a pointer to a string (hexadecimal) string ptr2str(void *p); // Convert a bool to a string: "false", "true" string bool2str(bool b); // Convert time/date to string // The format parameter is a strftime() format string string time2str(time_t t, const char *format); string current_time2str(const char *format); string weekday2str(int wkday); string month2str(int month); // Convert a full month name to an int (0-11) int str2month_full(const string &month); // Convert a duration in seconds to a string with hours, minutes seconds. // The hours and minutes are only present if there is at least 1 hour/minute. // E.g. 65s -> "1m 5s" // 3601s -> "1h 0m 1s" string duration2str(unsigned long seconds); // Convert a timer in seconds to a string (h:mm:ss) string timer2str(unsigned long seconds); /** * Convert a hex string to an integer. * @param h [in] A hex string. * @return The integer. */ unsigned long hex2int(const string &h); /** * Convert a hex string to a binary blob representing the hex value. * @param h [in] A hex string. * @param buf [in] A pointer to a buffer to store the binary blob. * @pre The buffer must be large enough to contain the binary blob. * @post buf contains the binary representation of the hex string. */ void hex2binary(const string &h, uint8 *buf); /** * Convert a binary blob to a hexadecimal string. * @param buf [in] Pointer to the binary blob. * @param len [in] Length of the blob. * @return The hexadecimal string. */ string binary2hex(uint8 *buf, unsigned long len); // Convert a string to lower case string tolower(const string &s); // Convert a string to upper case string toupper(const string &s); // Trim a string string rtrim(const string &s); string ltrim(const string &s); string trim(const string &s); /** * Pad a string on the left side till a certain length. * @param s [in] The string to pad. * @param c [in] The pad character. * @param len [in] The length to which the string must be padded. * @return The padded string. */ string padleft(const string &s, char c, unsigned long len); // Compare 2 strings case insensive, return // -1 --> s1 < s2 // 0 --> s1 == s2 // 1 --> s1 > s2 int cmp_nocase(const string &s1, const string &s2); // Return true if a string must be quoted in text encoding bool must_quote(const string &s); // Escape character c in string by prepending it with a backslash. // Backslashed are automatically escaped as well string escape(const string &s, char c); // Unescape a string string unescape(const string &s); // Escape reserved chars in s by there hex-notation (%HEX) // All chars that are not in unreserved are considered as reserved. string escape_hex(const string &s, const string &unreserved); // Unescape the hex-values in a string string unescape_hex(const string &s); // Replace all occurrences of 'from' char 'to' char in s string replace_char(const string &s, char from, char to); // Replace first occurrence of 'from'-string to 'to'-string in s string replace_first(const string &s, const string &from, const string &to); /** * Split a string into elements using a single character as separator. * @param s [in] The string to split. * @param c [in] The character separator. * @return Vector containing the split parts. */ vector split(const string &s, char c); /** * Split a string into elements using a string separator. * @param s [in] The string to split. * @param separator [in] The string separator. * @return Vector containing the split parts. */ vector split(const string &s, const string &separator); /** * Split a string into elements using line breaks as seperator * If the string contains a CRLF, then CRLF is used as line break. * Otherwise if the string contains a CR, then CR is used as line break. * Otherwise LF is used as line break. * @param s [in] The string to split. * @return Vector containing the split parts. */ vector split_linebreak(const string &s); /** * Split a string in two on the first occurrence of a separator. * @param s [in] The string to split. * @param c [in] The separator. * @return Vector containing the split parts. */ vector split_on_first(const string &s, char c); /** * Split a string in two on the last occurrence of a separator. * @param s [in] The string to split. * @param c [in] The separator. * @return Vector containing the split parts. */ vector split_on_last(const string &s, char c); // Split an escaped string into elements using c as a separator // Escaped means: \c will not be seen as a seperator and backslash is // escaped itself (\\) vector split_escaped(const string &s, char c); // Split a string into elements using spaces as separator // If quote_sensitive = true, then spaces within quoted strings will // not be used to split the string. vector split_ws(const string &s, bool quote_sensitive = false); /** * Join a vector of strings into one string. * @param v Vector of strings. * @param separator String to be inserted between the strings to join. * @return A string containing the concatenarion of all strings in v. * The invidual strings are separated by separator. */ string join_strings(const vector &v, const string &separator); // Remove surrounding quotes of a string if present. string unquote(const string &s); // Check if a string is a number bool is_number(const string &s); // Check if a string is an IP address bool is_ipaddr(const string &s); // Conversion between yes/no values and bool bool yesno2bool(const string &yesno); string bool2yesno(bool b); // Convert a text string to DTMF digits // Characters that cannot be converted will be removed string str2dtmf(const string &s); // Return true if string s looks like a phone number // A string looks like a phone number if it consists of digits, // *, #, special symbols and white space bool looks_like_phone(const string &s, const string &special_symbols); /** * Remove all special symbols from a string. * @param s [in] The string to convert. * @param special_symbols [in] The special symbols to remove. * @return The string without the special symbols. */ string remove_symbols(const string &s, const string &special_symbols); /** * Remove spaces and tabs from a string. * @param s [in] The string to convert. * @return The string without spaces and tabs. */ string remove_white_space(const string &s); /** * Truncate a string. If the string was longer than the truncated * result, then "..." will be appended. * @param s [in] The string to truncate. * @param len [in] The length in bytes to truncate to. * @return The truncated string. */ string dotted_truncate(const string &s, string::size_type len); /** * Convert a string to a printable representation, i.e. change * all non-printable chars into dots. * @param s [in] The string to convert. * @return The converted string. */ string to_printable(const string &s); /** * Get the error message describing an error number. * @param errnum [in] The error number. * @return The error message. */ string get_error_str(int errnum); #endif twinkle-1.10.1/src/utils/000077500000000000000000000000001277565361200152145ustar00rootroot00000000000000twinkle-1.10.1/src/utils/CMakeLists.txt000066400000000000000000000002311277565361200177500ustar00rootroot00000000000000project(libtwinkle-utils) set(LIBTWINKLE_UTILS-SRCS file_utils.cpp mime_database.cpp ) add_library(libtwinkle-utils OBJECT ${LIBTWINKLE_UTILS-SRCS}) twinkle-1.10.1/src/utils/file_utils.cpp000066400000000000000000000055471277565361200200720ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "file_utils.h" #include "util.h" #include #include #include #include #include #include using namespace std; using namespace utils; bool utils::filecopy(const string &from, const string &to) { ifstream from_file(from.c_str()); if (!from_file) { return false; } ofstream to_file(to.c_str()); if (!to_file) { return false; } to_file << from_file.rdbuf(); if (!to_file.good() || !from_file.good()) { return false; } return true; } string utils::strip_path_from_filename(const string &filename) { vector v = split_on_last(filename, PATH_SEPARATOR); return v.back(); } string utils::get_path_from_filename(const string &filename) { vector v = split_on_last(filename, PATH_SEPARATOR); if (v.size() == 1) { // There is no path. return ""; } return v.front(); } string utils::get_extension_from_filename(const string &filename) { vector v = split_on_last(filename, '.'); if (v.size() == 1) { // There is no file extension. return ""; } else { return v.back(); } } string utils::apply_glob_to_filename(const string &filename, const string &glob) { string name = strip_path_from_filename(filename); string path = get_path_from_filename(filename); string new_name = glob; string::size_type idx = new_name.find('*'); if (idx == string::npos) { // The glob expression does not contain a wild card to replace. return filename; } new_name.replace(new_name.begin() + idx, new_name.begin() + idx + 1, name); string new_filename = path; if (!new_filename.empty()) { new_filename += PATH_SEPARATOR; } new_filename += new_name; return new_filename; } string get_working_dir(void) { size_t buf_size = 1024; char *buf = (char*)malloc(buf_size); char *dir = NULL; while (true) { if ((dir = getcwd(buf, buf_size)) != NULL) break; if (errno != ERANGE) break; // The buffer is too small. // Avoid eternal looping. if (buf_size > 8192) break; // Increase the buffer size free(buf); buf_size *= 2; buf = (char*)malloc(buf_size); } string result; if (dir) result = dir; free(buf); return result; } twinkle-1.10.1/src/utils/file_utils.h000066400000000000000000000044761277565361200175370ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * File utilities */ #ifndef _FILE_UTILS_H #define _FILE_UTILS_H #include using namespace std; namespace utils { /** Separator to split parts in a file path. */ #define PATH_SEPARATOR '/' /** * Copy a file. * @param from [in] Absolute path of file to copy. * @param to [in] Absolute path of destination file. * @return true if copy succeeded, otherwise false. */ bool filecopy(const string &from, const string &to); /** * Strip the path to a file from an absolute file name. * @return The remaining file name without the full path. */ string strip_path_from_filename(const string &filename); /** * Get path to a file from an absolute file name. * @return The path name. */ string get_path_from_filename(const string &filename); /** * Get the extension from a file name. * @return The extension (without the initial dot). * @return Empty string if the file name does not have an extension. */ string get_extension_from_filename(const string &filename); /** * Apply a glob expression to a filename. * E.g. /tmp/twinkle with glob *.txt gives /tmp/twinkle.txt * /tmp/twinkle with README* give /tmp/READMEtwinkle.txt * @param filename [in] The filename. * @param glob [in] The glob expression to apply. * @return The modified filename. */ string apply_glob_to_filename(const string &filename, const string &glob); /** * Get the absolute path of the current working directory. * @return The absolute path of the current working directory. * @return Empty string if the current working directory cannot be determined. */ string get_working_dir(void); }; // namespace #endif twinkle-1.10.1/src/utils/mime_database.cpp000066400000000000000000000053151277565361200204770ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include "mime_database.h" #include #include "log.h" #include "sys_settings.h" using namespace utils; ////////////////////////// // class t_mime_db_record ////////////////////////// bool t_mime_db_record::create_file_record(vector &v) const { // The mime database is read only. So this function should // never be called. assert(false); return false; } bool t_mime_db_record::populate_from_file_record(const vector &v) { // Check number of fields if (v.size() != 2) return false; mimetype = v[0]; file_glob = v[1]; return true; } ////////////////////////// // class t_mime_database ////////////////////////// t_mime_database::t_mime_database() { set_separator(':'); set_filename(sys_config->get_mime_shared_database()); mime_magic_ = magic_open(MAGIC_MIME | MAGIC_ERROR); if (mime_magic_ == (magic_t)NULL) { log_file->write_report("Failed to open magic number database", "t_mime_database::t_mime_database", LOG_NORMAL, LOG_WARNING); return; } magic_load(mime_magic_, NULL); } t_mime_database::~t_mime_database() { magic_close(mime_magic_); } void t_mime_database::add_record(const t_mime_db_record &record) { map_mime2glob_.insert(make_pair(record.mimetype, record.file_glob)); } string t_mime_database::get_glob(const string &mimetype) const { map::const_iterator it = map_mime2glob_.find(mimetype); if (it != map_mime2glob_.end()) { return it->second; } return ""; } string t_mime_database::get_mimetype(const string &filename) const { const char *mime_desc = magic_file(mime_magic_, filename.c_str()); if (!mime_desc) return ""; // Sometimes the magic libary adds additional info to the // returned mime type. Strip this info. string mime_type(mime_desc); string::size_type end_of_mime = mime_type.find_first_not_of( "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQSTUVWXYZ" "0123456789-.!%*_+`'~/"); if (end_of_mime != string::npos) { mime_type = mime_type.substr(0, end_of_mime); } return mime_type; } twinkle-1.10.1/src/utils/mime_database.h000066400000000000000000000045171277565361200201470ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * Mime database * Conversion between mime types, file content and file extensions. */ #ifndef _MIME_DATABASE_H #define _MIME_DATABASE_H #include #include #include #include "record_file.h" using namespace std; namespace utils { /** Record from the mime database. */ class t_mime_db_record : public utils::t_record { public: string mimetype; /**< Mimetype, e.g. text/plain */ string file_glob; /**< File glob expression */ virtual bool create_file_record(vector &v) const; virtual bool populate_from_file_record(const vector &v); }; /** * The mime database. * The default location for the mime database is /usr/share/mime/globs */ class t_mime_database : public utils::t_record_file { private: /** Mapping between mimetypes and file globs. */ map map_mime2glob_; /** Handle on the magic number database. */ magic_t mime_magic_; protected: virtual void add_record(const t_mime_db_record &record); public: /** Constructor */ t_mime_database(); /** Destructor */ ~t_mime_database(); /** * Get a glob expression for a mimetype. * @param mimetype [in] The mimetype. * @return Glob expression associated with the mimetype. Empty string * if no glob expression can be found. */ string get_glob(const string &mimetype) const; /** * Get the mimetype of a file. * @param filename [in] Name of the file. * @return The mimetype or empty string if no mimetype can be determined. */ string get_mimetype(const string &filename) const; }; }; // namespace extern utils::t_mime_database *mime_database; #endif twinkle-1.10.1/src/utils/record_file.h000066400000000000000000000075231277565361200176510ustar00rootroot00000000000000/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ /** * @file * File to store data records. */ #ifndef _RECORD_FILE_H #define _RECORD_FILE_H #include #include #include #include #include #include #include #include "translator.h" #include "util.h" #include "threads/mutex.h" using namespace std; namespace utils { /** A single record in a file. */ class t_record { public: virtual ~t_record() {}; /** * Create a record to write to a file. * @param v [out] Vector of fields of record. * @return true, if record is successfully created. * @return false, otherwise. */ virtual bool create_file_record(vector &v) const = 0; /** * Populate from a file record. * @param v [in] Vector containing the fields of the record. * @return true, if record is successfully populated. * @return false, if file record could not be parsed. */ virtual bool populate_from_file_record(const vector &v) = 0; }; /** * A file containing records with a fixed number of fields. * @param R Subclass of @ref t_record */ template< class R > class t_record_file { private: /** Separator to separate fields in a file record. */ char field_separator; /** Header string to write as comment at start of file. */ string header; /** Name of the file containing the records. */ string filename; /** * Split a record into separate fields. * @param record [in] A complete record. * @param v [out] Vector of fields. */ void split_record(const string &record, vector &v) const; /** * Join fields of a record into a string. * Separator and comment symbols will be escaped. * @param v [in] Vector of fields. * @return Joined fields. */ string join_fields(const vector &v) const; protected: /** Mutex to protect concurrent access/ */ mutable t_recursive_mutex mtx_records; /** Records in the file. */ list records; /** * Add a record to the file. * @param record [in] Record to add. */ virtual void add_record(const R &record); public: /** Constructor. */ t_record_file(); /** Constructor. */ t_record_file(const string &_header, char _field_separator, const string &_filename); /** Destructor. */ virtual ~t_record_file() {}; /** @name Setters */ //@{ void set_header(const string &_header); void set_separator(char _separator); void set_filename(const string &_filename); //@} /** @name Getters */ //@{ list *get_records(); //@} /** * Load records from file. * @param error_msg [out] Error message on failure return. * @return true, if file was read successfully. * @return false, if it fails. error_msg is an error to be given to * the user. */ virtual bool load(string &error_msg); /** * Save records to file. * @param error_msg [out] Error message on failure return. * @return true, if file was saved successfully. * @return false, if it fails. error_msg is an error to be given to * the user. */ virtual bool save(string &error_msg) const; typedef typename list::const_iterator record_const_iterator; typedef typename list::iterator record_iterator; }; #include "record_file.hpp" }; // namespace #endif twinkle-1.10.1/src/utils/record_file.hpp000066400000000000000000000102521277565361200202020ustar00rootroot00000000000000/* Copyright (C) 2005-2007 Michel de Boer 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, see . */ #define COMMENT_SYMBOL '#' template< class R > void t_record_file::split_record(const string &record, vector &v) const { v = split_escaped(record, field_separator); for (vector::iterator it = v.begin(); it != v.end(); ++it) { *it = unescape(*it); } } template< class R > string t_record_file::join_fields(const vector &v) const { string s; for (vector::const_iterator it = v.begin(); it != v.end(); ++it) { if (it != v.begin()) s += field_separator; // Escape comment symbol. if (!it->empty() && it->at(0) == COMMENT_SYMBOL) s += '\\'; s += escape(*it, field_separator); } return s; } template< class R > void t_record_file::add_record(const R &record) { records.push_back(record); } template< class R > t_record_file::t_record_file(): field_separator('|') {} template< class R > t_record_file::t_record_file(const string &_header, char _field_separator, const string &_filename) : header(_header), field_separator(_field_separator), filename(_filename) {} template< class R > void t_record_file::set_header(const string &_header) { header = _header; } template< class R > void t_record_file::set_separator(char _separator) { field_separator = _separator; } template< class R > void t_record_file::set_filename(const string &_filename) { filename = _filename; } template< class R > list *t_record_file::get_records() { return &records; } template< class R > bool t_record_file::load(string &error_msg) { struct stat stat_buf; mtx_records.lock(); records.clear(); // Check if file exists if (filename.empty() || stat(filename.c_str(), &stat_buf) != 0) { // There is no file. mtx_records.unlock(); return true; } // Open call ile ifstream file(filename.c_str()); if (!file) { error_msg = TRANSLATE("Cannot open file for reading: %1"); error_msg = replace_first(error_msg, "%1", filename); mtx_records.unlock(); return false; } // Read and parse history file. while (!file.eof()) { string line; getline(file, line); // Check if read operation succeeded if (!file.good() && !file.eof()) { error_msg = TRANSLATE("File system error while reading file %1 ."); error_msg = replace_first(error_msg, "%1", filename); mtx_records.unlock(); return false; } line = trim(line); // Skip empty lines if (line.size() == 0) continue; // Skip comment lines if (line[0] == COMMENT_SYMBOL) continue; // Add record. Skip records that cannot be parsed. R record; vector v; split_record(line, v); if (record.populate_from_file_record(v)) { add_record(record); } } mtx_records.unlock(); return true; } template< class R > bool t_record_file::save(string &error_msg) const { if (filename.empty()) return false; mtx_records.lock(); // Open file ofstream file(filename.c_str()); if (!file) { error_msg = TRANSLATE("Cannot open file for writing: %1"); error_msg = replace_first(error_msg, "%1", filename); mtx_records.unlock(); return false; } // Write file header file << "# " << header << endl; // Write records for (record_const_iterator i = records.begin(); i != records.end(); ++i) { vector v; if (i->create_file_record(v)) { file << join_fields(v); file << endl; } } mtx_records.unlock(); if (!file.good()) { error_msg = TRANSLATE("File system error while writing file %1 ."); error_msg = replace_first(error_msg, "%1", filename); return false; } return true; } twinkle-1.10.1/twinkle.desktop.in000066400000000000000000000035711277565361200167500ustar00rootroot00000000000000[Desktop Entry] Name=Twinkle GenericName=A SIP softphone GenericName[cy]=Ffôn meddal SIP GenericName[da]=SIP IP-telefoni GenericName[el]=Λογισμικό τηλέφωνο SIP GenericName[es]=Un teléfono liviano SIP GenericName[et]=SIP-arvutitelefon GenericName[eu]=SIP softfono bat GenericName[fi]=SIP-ohjelmistopuhelin GenericName[fr]=Téléphone logiciel SIP GenericName[gl]=Un teléfono SIP por software GenericName[hu]=Szoftveres telefon (SIP) GenericName[it]=Telefono VoIP SIP GenericName[ja]=SIP ビデオ電話 GenericName[nb]=SIP IP-telefoni GenericName[nl]=Een SIP-softphone GenericName[nn]=SIP IP-telefoni GenericName[pl]=Telefon SIP GenericName[pt]=Um softphone SIP GenericName[pt_BR]=Um softphone SIP GenericName[ro]=Un program de telefonie SIP GenericName[ru]=Программный телефон SIP GenericName[sl]=Programski telefon za SIP GenericName[sv]=En SIP Videotelefon GenericName[tr]=Bir SIP telefon GenericName[uk]=Програмний телефон SIP Comment=Voice over Internet Protocol (VoIP) SIP Phone Comment[cs]=VoIP (Voice over Internet Protocol) telefon SIP Comment[da]=Voice over Internet Protocol (VoIP) SIP-telefon Comment[de]=VoIP-SIP-Softwaretelefon Comment[ko]=Voice over Internet Protocol (VoIP) SIP 폰 Comment[it]=Telefono SIP VoIP (Voice over Internet Protocol) Comment[ja]=Voice over Internet Protocol (VoIP) SIP 電話 Comment[pl]=Aplikacja telefoniczna do VoIP (Voice over Internet Protocol), wykorzystująca SIP Comment[pt]=Telefone SIP Voz sobre IP (Voice over Internet Protocol - VoIP) Comment[pt_BR]=Telefone SIP de voz sobre protocolo de internet (VoIP) Comment[ru]=VoIP (передача голоса по IP-протоколу) SIP-телефон Comment[uk]=VoIP (передача голосу по IP-протоколу) SIP-телефон Type=Application Exec=twinkle Icon=twinkle StartupNotify=true Terminal=false Categories=Qt;Network;Telephony; twinkle-1.10.1/twinkle.spec.in000066400000000000000000000032641277565361200162300ustar00rootroot00000000000000Summary: A SIP Soft Phone Name: twinkle Version: @VERSION@ Release: %{suserel}1 License: GPL Group: Productivity/Telephony/SIP/Clients Source: %{name}-%{version}.tar.gz Prefix: %{_prefix} BuildArch: i586 #BuildArch: x86_64 BuildRoot: %{_tmppath}/making_of_%{name}_%{version} Packager: URL: http://www.twinklephone.com Requires: alsa Requires: ucommon Requires: ccrtp >= 1.6.0 Requires: libzrtpcpp >= 1.3.0 Requires: kdelibs3 >= 3.2.0 Requires: libsndfile Requires: libspeex >= 1.1.99 Requires: libxml2 Requires: file Requires: readline BuildRequires: alsa-devel BuildRequires: qt3-devel BuildRequires: update-desktop-files BuildRequires: ucommon-devel BuildRequires: libccrtp-devel BuildRequires: libzrtpcpp-devel BuildRequires: kdelibs3-devel BuildRequires: libsndfile-devel BuildRequires: speex-devel BuildRequires: boost-devel BuildRequires: libxml2-devel BuildRequires: file-devel BuildRequires: readline-devel %description Twinkle is a SIP based softphone for making telephone calls and instant messaging over IP networks. %prep %setup -q %build autoreconf -fi %configure --without-ilbc #%configure --without-ilbc --enable-libsuffix=64 #sed -i -e "s|(INCPATH *= \)|\1-I/opt/kde3/include |" src/gui/Makefile make %install rm -rf %{buildroot} %makeinstall install -d 755 %{buildroot}%{_datadir}/pixmaps install -m 644 src/gui/images/twinkle48.png %{buildroot}%{_datadir}/pixmaps/twinkle.png %suse_update_desktop_file -c twinkle Twinkle "A SIP softphone" twinkle twinkle Network Telephony %clean rm -rf %{buildroot} %files %defattr(-, root, root) %doc AUTHORS COPYING README ChangeLog %{_bindir}/%{name} %{_datadir}/%{name} %{_datadir}/pixmaps/twinkle.png %{_datadir}/applications/twinkle.desktop twinkle-1.10.1/twinkle_config.h.in000066400000000000000000000005731277565361200170520ustar00rootroot00000000000000#cmakedefine WITH_DIAMONDCARD #cmakedefine HAVE_SPEEX #cmakedefine HAVE_ILBC #cmakedefine HAVE_ZRTP #cmakedefine HAVE_BCG729 #cmakedefine HAVE_GSM #cmakedefine HAVE_UNISTD_H #cmakedefine HAVE_LINUX_TYPES_H #cmakedefine HAVE_LINUX_ERRQUEUE_H #cmakedefine HAVE_LIBASOUND #define VERSION "${PRODUCT_VERSION}" #define VERSION_DATE "${PRODUCT_DATE}" #define DATADIR "${datadir}"