From e2bc6f4153813cc570ae814c8ddb74628009b488 Mon Sep 17 00:00:00 2001 From: Michal Kubecek Date: Mon, 13 Apr 2015 09:21:39 +0200 Subject: initial checkin Check in contents of upstream 1.4.2 tarball, exclude generated files. --- .gitignore | 39 + AUTHORS | 35 + COPYING | 340 + ChangeLog | 796 ++ Doxyfile | 1252 +++ Makefile.am | 31 + NEWS | 796 ++ README | 253 + THANKS | 16 + acinclude.m4 | 11630 ++++++++++++++++++++++++ configure.in | 346 + data/providers.csv | 6 + data/ringback.wav | Bin 0 -> 16878 bytes data/ringtone.wav | Bin 0 -> 44669 bytes m4/vl_lib_readline.m4 | 107 + sip.protocol | 12 + src/Makefile.am | 104 + src/abstract_dialog.cpp | 325 + src/abstract_dialog.h | 342 + src/address_book.cpp | 193 + src/address_book.h | 125 + src/audio/Makefile.am | 49 + src/audio/README_G711 | 94 + src/audio/audio_codecs.cpp | 99 + src/audio/audio_codecs.h | 107 + src/audio/audio_decoder.cpp | 523 ++ src/audio/audio_decoder.h | 201 + src/audio/audio_device.cpp | 909 ++ src/audio/audio_device.h | 126 + src/audio/audio_encoder.cpp | 430 + src/audio/audio_encoder.h | 183 + src/audio/audio_rx.cpp | 915 ++ src/audio/audio_rx.h | 217 + src/audio/audio_session.cpp | 677 ++ src/audio/audio_session.h | 182 + src/audio/audio_tx.cpp | 1009 ++ src/audio/audio_tx.h | 205 + src/audio/dtmf_player.cpp | 183 + src/audio/dtmf_player.h | 98 + src/audio/freq_gen.cpp | 134 + src/audio/freq_gen.h | 48 + src/audio/g711.cpp | 311 + src/audio/g711.h | 18 + src/audio/g721.cpp | 174 + src/audio/g723_16.cpp | 181 + src/audio/g723_24.cpp | 159 + src/audio/g723_40.cpp | 179 + src/audio/g72x.cpp | 565 ++ src/audio/g72x.h | 151 + src/audio/gsm/COPYRIGHT | 16 + src/audio/gsm/ChangeLog | 80 + src/audio/gsm/INSTALL | 99 + src/audio/gsm/MACHINES | 11 + src/audio/gsm/Makefile.am | 32 + src/audio/gsm/README | 37 + src/audio/gsm/inc/config.h | 37 + src/audio/gsm/inc/gsm.h | 71 + src/audio/gsm/inc/private.h | 268 + src/audio/gsm/inc/proto.h | 65 + src/audio/gsm/inc/unproto.h | 23 + src/audio/gsm/src/add.cpp | 235 + src/audio/gsm/src/code.cpp | 100 + src/audio/gsm/src/debug.cpp | 76 + src/audio/gsm/src/decode.cpp | 63 + src/audio/gsm/src/gsm_create.cpp | 45 + src/audio/gsm/src/gsm_decode.cpp | 361 + src/audio/gsm/src/gsm_destroy.cpp | 26 + src/audio/gsm/src/gsm_encode.cpp | 451 + src/audio/gsm/src/gsm_explode.cpp | 417 + src/audio/gsm/src/gsm_implode.cpp | 515 ++ src/audio/gsm/src/gsm_option.cpp | 69 + src/audio/gsm/src/gsm_print.cpp | 167 + src/audio/gsm/src/long_term.cpp | 949 ++ src/audio/gsm/src/lpc.cpp | 341 + src/audio/gsm/src/preprocess.cpp | 113 + src/audio/gsm/src/rpe.cpp | 488 + src/audio/gsm/src/short_term.cpp | 429 + src/audio/gsm/src/table.cpp | 63 + src/audio/media_buffer.cpp | 128 + src/audio/media_buffer.h | 73 + src/audio/rtp_telephone_event.cpp | 83 + src/audio/rtp_telephone_event.h | 79 + src/audio/tone_gen.cpp | 245 + src/audio/tone_gen.h | 68 + src/audio/twinkle_rtp_session.cpp | 117 + src/audio/twinkle_rtp_session.h | 53 + src/audio/twinkle_zrtp_ui.cpp | 296 + src/audio/twinkle_zrtp_ui.h | 90 + src/audits/Makefile.am | 9 + src/audits/memman.cpp | 238 + src/audits/memman.h | 87 + src/auth.cpp | 231 + src/auth.h | 153 + src/call_history.cpp | 455 + src/call_history.h | 227 + src/call_script.cpp | 459 + src/call_script.h | 199 + src/client_request.cpp | 124 + src/client_request.h | 127 + src/cmd_socket.cpp | 200 + src/cmd_socket.h | 66 + src/dialog.cpp | 3825 ++++++++ src/dialog.h | 845 ++ src/diamondcard.cpp | 126 + src/diamondcard.h | 68 + src/epa.cpp | 516 ++ src/epa.h | 216 + src/events.cpp | 726 ++ src/events.h | 840 ++ src/exceptions.h | 38 + src/gui/address_finder.cpp | 123 + src/gui/address_finder.h | 79 + src/gui/addresscardform.ui | 234 + src/gui/addresscardform.ui.h | 73 + src/gui/addresslistviewitem.cpp | 41 + src/gui/addresslistviewitem.h | 36 + src/gui/authenticationform.ui | 340 + src/gui/authenticationform.ui.h | 44 + src/gui/buddyform.ui | 255 + src/gui/buddyform.ui.h | 153 + src/gui/buddylistview.cpp | 276 + src/gui/buddylistview.h | 92 + src/gui/command_args.h | 61 + src/gui/core_strings.h | 108 + src/gui/deregisterform.ui | 105 + src/gui/deregisterform.ui.h | 26 + src/gui/diamondcardprofileform.ui | 307 + src/gui/diamondcardprofileform.ui.h | 151 + src/gui/dtmfform.ui | 509 ++ src/gui/dtmfform.ui.h | 177 + src/gui/freedesksystray.cpp | 165 + src/gui/freedesksystray.h | 94 + src/gui/getaddressform.ui | 476 + src/gui/getaddressform.ui.h | 259 + src/gui/getprofilenameform.ui | 174 + src/gui/getprofilenameform.ui.h | 80 + src/gui/gui.cpp | 3057 +++++++ src/gui/gui.h | 396 + src/gui/historyform.ui | 475 + src/gui/historyform.ui.h | 358 + src/gui/historylistview.cpp | 89 + src/gui/historylistview.h | 52 + src/gui/images/1downarrow.png | Bin 0 -> 579 bytes src/gui/images/1leftarrow-yellow.png | Bin 0 -> 657 bytes src/gui/images/1leftarrow.png | Bin 0 -> 614 bytes src/gui/images/1rightarrow.png | Bin 0 -> 630 bytes src/gui/images/1uparrow.png | Bin 0 -> 586 bytes src/gui/images/answer-disabled.png | Bin 0 -> 370 bytes src/gui/images/answer.png | Bin 0 -> 381 bytes src/gui/images/attach.png | Bin 0 -> 712 bytes src/gui/images/auto_answer-disabled.png | Bin 0 -> 520 bytes src/gui/images/auto_answer.png | Bin 0 -> 809 bytes src/gui/images/buddy.png | Bin 0 -> 737 bytes src/gui/images/bye-disabled.png | Bin 0 -> 389 bytes src/gui/images/bye.png | Bin 0 -> 423 bytes src/gui/images/cancel-disabled.png | Bin 0 -> 515 bytes src/gui/images/cancel.png | Bin 0 -> 890 bytes src/gui/images/cf-disabled.png | Bin 0 -> 441 bytes src/gui/images/cf.png | Bin 0 -> 628 bytes src/gui/images/clock.png | Bin 0 -> 2429 bytes src/gui/images/conf-disabled.png | Bin 0 -> 573 bytes src/gui/images/conf.png | Bin 0 -> 378 bytes src/gui/images/conference-disabled.png | Bin 0 -> 375 bytes src/gui/images/conference.png | Bin 0 -> 456 bytes src/gui/images/consult-xfer.png | Bin 0 -> 817 bytes src/gui/images/contexthelp.png | Bin 0 -> 799 bytes src/gui/images/dtmf-0.png | Bin 0 -> 227 bytes src/gui/images/dtmf-1.png | Bin 0 -> 155 bytes src/gui/images/dtmf-2.png | Bin 0 -> 495 bytes src/gui/images/dtmf-3.png | Bin 0 -> 496 bytes src/gui/images/dtmf-4.png | Bin 0 -> 420 bytes src/gui/images/dtmf-5.png | Bin 0 -> 393 bytes src/gui/images/dtmf-6.png | Bin 0 -> 474 bytes src/gui/images/dtmf-7.png | Bin 0 -> 481 bytes src/gui/images/dtmf-8.png | Bin 0 -> 463 bytes src/gui/images/dtmf-9.png | Bin 0 -> 627 bytes src/gui/images/dtmf-a.png | Bin 0 -> 232 bytes src/gui/images/dtmf-b.png | Bin 0 -> 236 bytes src/gui/images/dtmf-c.png | Bin 0 -> 239 bytes src/gui/images/dtmf-d.png | Bin 0 -> 223 bytes src/gui/images/dtmf-disabled.png | Bin 0 -> 374 bytes src/gui/images/dtmf-pound.png | Bin 0 -> 245 bytes src/gui/images/dtmf-star.png | Bin 0 -> 247 bytes src/gui/images/dtmf.png | Bin 0 -> 354 bytes src/gui/images/edit.png | Bin 0 -> 1242 bytes src/gui/images/edit16.png | Bin 0 -> 636 bytes src/gui/images/editcopy | Bin 0 -> 268 bytes src/gui/images/editcut | Bin 0 -> 214 bytes src/gui/images/editdelete.png | Bin 0 -> 695 bytes src/gui/images/editpaste | Bin 0 -> 293 bytes src/gui/images/encrypted-disabled.png | Bin 0 -> 481 bytes src/gui/images/encrypted.png | Bin 0 -> 693 bytes src/gui/images/encrypted32.png | Bin 0 -> 1583 bytes src/gui/images/encrypted_verified.png | Bin 0 -> 844 bytes src/gui/images/exit.png | Bin 0 -> 777 bytes src/gui/images/favorites.png | Bin 0 -> 4156 bytes src/gui/images/filenew | Bin 0 -> 184 bytes src/gui/images/fileopen-disabled.png | Bin 0 -> 495 bytes src/gui/images/fileopen.png | Bin 0 -> 818 bytes src/gui/images/filesave | Bin 0 -> 230 bytes src/gui/images/gear.png | Bin 0 -> 799 bytes src/gui/images/hold-disabled.png | Bin 0 -> 329 bytes src/gui/images/hold.png | Bin 0 -> 488 bytes src/gui/images/invite-disabled.png | Bin 0 -> 318 bytes src/gui/images/invite.png | Bin 0 -> 338 bytes src/gui/images/kcmpci.png | Bin 0 -> 2404 bytes src/gui/images/kcmpci16.png | Bin 0 -> 836 bytes src/gui/images/kmix.png | Bin 0 -> 1942 bytes src/gui/images/knotify.png | Bin 0 -> 1207 bytes src/gui/images/kontact_contacts-disabled.png | Bin 0 -> 521 bytes src/gui/images/kontact_contacts.png | Bin 0 -> 840 bytes src/gui/images/kontact_contacts32.png | Bin 0 -> 3039 bytes src/gui/images/log.png | Bin 0 -> 1067 bytes src/gui/images/log_small.png | Bin 0 -> 838 bytes src/gui/images/message.png | Bin 0 -> 496 bytes src/gui/images/message32.png | Bin 0 -> 959 bytes src/gui/images/mime_application.png | Bin 0 -> 966 bytes src/gui/images/mime_audio.png | Bin 0 -> 1950 bytes src/gui/images/mime_image.png | Bin 0 -> 1892 bytes src/gui/images/mime_text.png | Bin 0 -> 1250 bytes src/gui/images/mime_video.png | Bin 0 -> 2028 bytes src/gui/images/missed-disabled.png | Bin 0 -> 411 bytes src/gui/images/missed.png | Bin 0 -> 677 bytes src/gui/images/mute-disabled.png | Bin 0 -> 399 bytes src/gui/images/mute.png | Bin 0 -> 343 bytes src/gui/images/mwi_failure16.png | Bin 0 -> 898 bytes src/gui/images/mwi_new16.png | Bin 0 -> 823 bytes src/gui/images/mwi_none.png | Bin 0 -> 2112 bytes src/gui/images/mwi_none16.png | Bin 0 -> 790 bytes src/gui/images/mwi_none16_dis.png | Bin 0 -> 793 bytes src/gui/images/network.png | Bin 0 -> 2334 bytes src/gui/images/no-indication.png | Bin 0 -> 444 bytes src/gui/images/ok.png | Bin 0 -> 661 bytes src/gui/images/package_network.png | Bin 0 -> 2052 bytes src/gui/images/package_system.png | Bin 0 -> 2521 bytes src/gui/images/password.png | Bin 0 -> 3302 bytes src/gui/images/penguin-small.png | Bin 0 -> 964 bytes src/gui/images/penguin.png | Bin 0 -> 2016 bytes src/gui/images/penguin_big.png | Bin 0 -> 6775 bytes src/gui/images/presence.png | Bin 0 -> 764 bytes src/gui/images/presence_failed.png | Bin 0 -> 832 bytes src/gui/images/presence_offline.png | Bin 0 -> 849 bytes src/gui/images/presence_online.png | Bin 0 -> 848 bytes src/gui/images/presence_rejected.png | Bin 0 -> 896 bytes src/gui/images/presence_unknown.png | Bin 0 -> 688 bytes src/gui/images/print | Bin 0 -> 709 bytes src/gui/images/qt-logo.png | Bin 0 -> 650 bytes src/gui/images/redial-disabled.png | Bin 0 -> 385 bytes src/gui/images/redial.png | Bin 0 -> 425 bytes src/gui/images/redirect-disabled.png | Bin 0 -> 326 bytes src/gui/images/redirect.png | Bin 0 -> 387 bytes src/gui/images/redo | Bin 0 -> 204 bytes src/gui/images/reg-query.png | Bin 0 -> 824 bytes src/gui/images/reg_failed-disabled.png | Bin 0 -> 583 bytes src/gui/images/reg_failed.png | Bin 0 -> 950 bytes src/gui/images/reject-disabled.png | Bin 0 -> 361 bytes src/gui/images/reject.png | Bin 0 -> 392 bytes src/gui/images/save_as.png | Bin 0 -> 838 bytes src/gui/images/searchfind | Bin 0 -> 658 bytes src/gui/images/settings.png | Bin 0 -> 807 bytes src/gui/images/stat_conference.png | Bin 0 -> 852 bytes src/gui/images/stat_established.png | Bin 0 -> 830 bytes src/gui/images/stat_established_nomedia.png | Bin 0 -> 531 bytes src/gui/images/stat_mute.png | Bin 0 -> 696 bytes src/gui/images/stat_outgoing.png | Bin 0 -> 850 bytes src/gui/images/stat_ringing.png | Bin 0 -> 581 bytes src/gui/images/sys_auto_ans.png | Bin 0 -> 1468 bytes src/gui/images/sys_auto_ans_dis.png | Bin 0 -> 851 bytes src/gui/images/sys_busy_estab.png | Bin 0 -> 1464 bytes src/gui/images/sys_busy_estab_dis.png | Bin 0 -> 861 bytes src/gui/images/sys_busy_trans.png | Bin 0 -> 1338 bytes src/gui/images/sys_busy_trans_dis.png | Bin 0 -> 763 bytes src/gui/images/sys_dnd.png | Bin 0 -> 1463 bytes src/gui/images/sys_dnd_dis.png | Bin 0 -> 815 bytes src/gui/images/sys_encrypted.png | Bin 0 -> 1380 bytes src/gui/images/sys_encrypted_dis.png | Bin 0 -> 813 bytes src/gui/images/sys_encrypted_verified.png | Bin 0 -> 1471 bytes src/gui/images/sys_encrypted_verified_dis.png | Bin 0 -> 840 bytes src/gui/images/sys_hold.png | Bin 0 -> 1247 bytes src/gui/images/sys_hold_dis.png | Bin 0 -> 713 bytes src/gui/images/sys_idle.png | Bin 0 -> 1254 bytes src/gui/images/sys_idle_dis.png | Bin 0 -> 722 bytes src/gui/images/sys_missed.png | Bin 0 -> 1473 bytes src/gui/images/sys_missed_dis.png | Bin 0 -> 808 bytes src/gui/images/sys_mute.png | Bin 0 -> 1335 bytes src/gui/images/sys_mute_dis.png | Bin 0 -> 771 bytes src/gui/images/sys_mwi.png | Bin 0 -> 1422 bytes src/gui/images/sys_mwi_dis.png | Bin 0 -> 1431 bytes src/gui/images/sys_redir.png | Bin 0 -> 1510 bytes src/gui/images/sys_redir_dis.png | Bin 0 -> 865 bytes src/gui/images/sys_services.png | Bin 0 -> 1346 bytes src/gui/images/sys_services_dis.png | Bin 0 -> 760 bytes src/gui/images/telephone-hook.png | Bin 0 -> 616 bytes src/gui/images/transfer-disabled.png | Bin 0 -> 333 bytes src/gui/images/transfer.png | Bin 0 -> 424 bytes src/gui/images/twinkle16-disabled.png | Bin 0 -> 449 bytes src/gui/images/twinkle16.png | Bin 0 -> 669 bytes src/gui/images/twinkle24.png | Bin 0 -> 1254 bytes src/gui/images/twinkle32.png | Bin 0 -> 1892 bytes src/gui/images/twinkle48.png | Bin 0 -> 3461 bytes src/gui/images/undo | Bin 0 -> 212 bytes src/gui/images/yast_PhoneTTOffhook.png | Bin 0 -> 2821 bytes src/gui/images/yast_babelfish.png | Bin 0 -> 2177 bytes src/gui/inviteform.ui | 369 + src/gui/inviteform.ui.h | 153 + src/gui/lang/Makefile.am | 38 + src/gui/lang/twinkle_cs.ts | 6039 ++++++++++++ src/gui/lang/twinkle_de.ts | 6071 +++++++++++++ src/gui/lang/twinkle_fr.ts | 6038 ++++++++++++ src/gui/lang/twinkle_nl.ts | 6066 ++++++++++++ src/gui/lang/twinkle_ru.ts | 5729 ++++++++++++ src/gui/lang/twinkle_sv.ts | 5772 ++++++++++++ src/gui/lang/twinkle_xx.ts | 5669 ++++++++++++ src/gui/logviewform.ui | 123 + src/gui/logviewform.ui.h | 98 + src/gui/main.cpp | 1201 +++ src/gui/messageform.ui | 325 + src/gui/messageform.ui.h | 616 ++ src/gui/messageformview.cpp | 159 + src/gui/messageformview.h | 43 + src/gui/mphoneform.ui | 2851 ++++++ src/gui/mphoneform.ui.h | 3126 +++++++ src/gui/numberconversionform.ui | 175 + src/gui/numberconversionform.ui.h | 84 + src/gui/qt_translator.h | 43 + src/gui/redirectform.ui | 308 + src/gui/redirectform.ui.h | 157 + src/gui/selectnicform.ui | 212 + src/gui/selectnicform.ui.h | 89 + src/gui/selectprofileform.ui | 414 + src/gui/selectprofileform.ui.h | 631 ++ src/gui/selectuserform.ui | 206 + src/gui/selectuserform.ui.h | 138 + src/gui/sendfileform.ui | 217 + src/gui/sendfileform.ui.h | 106 + src/gui/srvredirectform.ui | 819 ++ src/gui/srvredirectform.ui.h | 389 + src/gui/syssettingsform.ui | 1854 ++++ src/gui/syssettingsform.ui.h | 489 + src/gui/termcapform.ui | 241 + src/gui/termcapform.ui.h | 101 + src/gui/textbrowsernoautolink.h | 48 + src/gui/transferform.ui | 270 + src/gui/transferform.ui.h | 195 + src/gui/twinkle.pro | 237 + src/gui/twinkleapplication.cpp | 47 + src/gui/twinkleapplication.h | 44 + src/gui/twinklesystray.cpp | 38 + src/gui/twinklesystray.h | 47 + src/gui/userprofileform.ui | 5758 ++++++++++++ src/gui/userprofileform.ui.h | 1458 +++ src/gui/wizardform.ui | 367 + src/gui/wizardform.ui.h | 263 + src/gui/yesnodialog.cpp | 86 + src/gui/yesnodialog.h | 56 + src/id_object.cpp | 41 + src/id_object.h | 65 + src/im/Makefile.am | 9 + src/im/im_iscomposing_body.cpp | 182 + src/im/im_iscomposing_body.h | 89 + src/im/msg_session.cpp | 423 + src/im/msg_session.h | 347 + src/line.cpp | 2279 +++++ src/line.h | 567 ++ src/listener.cpp | 616 ++ src/listener.h | 36 + src/log.cpp | 372 + src/log.h | 119 + src/main.cpp | 606 ++ src/mwi/Makefile.am | 29 + src/mwi/mwi.cpp | 74 + src/mwi/mwi.h | 68 + src/mwi/mwi_dialog.cpp | 35 + src/mwi/mwi_dialog.h | 35 + src/mwi/mwi_subscription.cpp | 105 + src/mwi/mwi_subscription.h | 43 + src/mwi/simple_msg_sum_body.cpp | 186 + src/mwi/simple_msg_sum_body.h | 103 + src/parser/Makefile.am | 195 + src/parser/challenge.cpp | 178 + src/parser/challenge.h | 66 + src/parser/coding.cpp | 42 + src/parser/coding.h | 39 + src/parser/credentials.cpp | 158 + src/parser/credentials.h | 69 + src/parser/definitions.cpp | 59 + src/parser/definitions.h | 81 + src/parser/hdr_accept.cpp | 47 + src/parser/hdr_accept.h | 44 + src/parser/hdr_accept_encoding.cpp | 42 + src/parser/hdr_accept_encoding.h | 43 + src/parser/hdr_accept_language.cpp | 66 + src/parser/hdr_accept_language.h | 52 + src/parser/hdr_alert_info.cpp | 56 + src/parser/hdr_alert_info.h | 53 + src/parser/hdr_allow.cpp | 75 + src/parser/hdr_allow.h | 45 + src/parser/hdr_allow_events.cpp | 42 + src/parser/hdr_allow_events.h | 41 + src/parser/hdr_auth_info.cpp | 99 + src/parser/hdr_auth_info.h | 47 + src/parser/hdr_authorization.cpp | 92 + src/parser/hdr_authorization.h | 51 + src/parser/hdr_call_id.cpp | 33 + src/parser/hdr_call_id.h | 38 + src/parser/hdr_call_info.cpp | 56 + src/parser/hdr_call_info.h | 53 + src/parser/hdr_contact.cpp | 178 + src/parser/hdr_contact.h | 100 + src/parser/hdr_content_disp.cpp | 60 + src/parser/hdr_content_disp.h | 51 + src/parser/hdr_content_encoding.cpp | 43 + src/parser/hdr_content_encoding.h | 43 + src/parser/hdr_content_language.cpp | 43 + src/parser/hdr_content_language.h | 43 + src/parser/hdr_content_length.cpp | 40 + src/parser/hdr_content_length.h | 39 + src/parser/hdr_content_type.cpp | 37 + src/parser/hdr_content_type.h | 36 + src/parser/hdr_cseq.cpp | 64 + src/parser/hdr_cseq.h | 46 + src/parser/hdr_date.cpp | 64 + src/parser/hdr_date.h | 40 + src/parser/hdr_error_info.cpp | 56 + src/parser/hdr_error_info.h | 53 + src/parser/hdr_event.cpp | 54 + src/parser/hdr_event.h | 52 + src/parser/hdr_expires.cpp | 36 + src/parser/hdr_expires.h | 39 + src/parser/hdr_from.cpp | 88 + src/parser/hdr_from.h | 58 + src/parser/hdr_in_reply_to.cpp | 42 + src/parser/hdr_in_reply_to.h | 39 + src/parser/hdr_max_forwards.cpp | 36 + src/parser/hdr_max_forwards.h | 39 + src/parser/hdr_mime_version.cpp | 33 + src/parser/hdr_mime_version.h | 37 + src/parser/hdr_min_expires.cpp | 36 + src/parser/hdr_min_expires.h | 39 + src/parser/hdr_organization.cpp | 33 + src/parser/hdr_organization.h | 37 + src/parser/hdr_p_asserted_identity.cpp | 43 + src/parser/hdr_p_asserted_identity.h | 41 + src/parser/hdr_p_preferred_identity.cpp | 43 + src/parser/hdr_p_preferred_identity.h | 41 + src/parser/hdr_priority.cpp | 33 + src/parser/hdr_priority.h | 37 + src/parser/hdr_privacy.cpp | 51 + src/parser/hdr_privacy.h | 48 + src/parser/hdr_proxy_authenticate.cpp | 33 + src/parser/hdr_proxy_authenticate.h | 41 + src/parser/hdr_proxy_authorization.cpp | 92 + src/parser/hdr_proxy_authorization.h | 50 + src/parser/hdr_proxy_require.cpp | 42 + src/parser/hdr_proxy_require.h | 37 + src/parser/hdr_rack.cpp | 64 + src/parser/hdr_rack.h | 47 + src/parser/hdr_record_route.cpp | 66 + src/parser/hdr_record_route.h | 44 + src/parser/hdr_refer_sub.cpp | 49 + src/parser/hdr_refer_sub.h | 46 + src/parser/hdr_refer_to.cpp | 70 + src/parser/hdr_refer_to.h | 47 + src/parser/hdr_referred_by.cpp | 80 + src/parser/hdr_referred_by.h | 49 + src/parser/hdr_replaces.cpp | 77 + src/parser/hdr_replaces.h | 51 + src/parser/hdr_reply_to.cpp | 68 + src/parser/hdr_reply_to.h | 46 + src/parser/hdr_request_disposition.cpp | 213 + src/parser/hdr_request_disposition.h | 111 + src/parser/hdr_require.cpp | 75 + src/parser/hdr_require.h | 41 + src/parser/hdr_retry_after.cpp | 69 + src/parser/hdr_retry_after.h | 46 + src/parser/hdr_route.cpp | 67 + src/parser/hdr_route.h | 47 + src/parser/hdr_rseq.cpp | 40 + src/parser/hdr_rseq.h | 43 + src/parser/hdr_server.cpp | 77 + src/parser/hdr_server.h | 55 + src/parser/hdr_service_route.cpp | 66 + src/parser/hdr_service_route.h | 44 + src/parser/hdr_sip_etag.cpp | 31 + src/parser/hdr_sip_etag.h | 49 + src/parser/hdr_sip_if_match.cpp | 36 + src/parser/hdr_sip_if_match.h | 52 + src/parser/hdr_subject.cpp | 34 + src/parser/hdr_subject.h | 37 + src/parser/hdr_subscription_state.cpp | 78 + src/parser/hdr_subscription_state.h | 63 + src/parser/hdr_supported.cpp | 72 + src/parser/hdr_supported.h | 49 + src/parser/hdr_timestamp.cpp | 51 + src/parser/hdr_timestamp.h | 40 + src/parser/hdr_to.cpp | 80 + src/parser/hdr_to.h | 48 + src/parser/hdr_unsupported.cpp | 59 + src/parser/hdr_unsupported.h | 39 + src/parser/hdr_user_agent.cpp | 47 + src/parser/hdr_user_agent.h | 44 + src/parser/hdr_via.cpp | 211 + src/parser/hdr_via.h | 73 + src/parser/hdr_warning.cpp | 89 + src/parser/hdr_warning.h | 83 + src/parser/hdr_www_authenticate.cpp | 33 + src/parser/hdr_www_authenticate.h | 41 + src/parser/header.cpp | 82 + src/parser/header.h | 66 + src/parser/identity.cpp | 51 + src/parser/identity.h | 40 + src/parser/media_type.cpp | 100 + src/parser/media_type.h | 84 + src/parser/milenage.cpp | 284 + src/parser/milenage.h | 35 + src/parser/parameter.cpp | 90 + src/parser/parameter.h | 60 + src/parser/parse_ctrl.cpp | 136 + src/parser/parse_ctrl.h | 131 + src/parser/parser.cxx | 5106 +++++++++++ src/parser/parser.h | 252 + src/parser/parser.yxx | 1594 ++++ src/parser/request.cpp | 769 ++ src/parser/request.h | 218 + src/parser/response.cpp | 237 + src/parser/response.h | 214 + src/parser/rijndael.cpp | 440 + src/parser/rijndael.h | 26 + src/parser/route.cpp | 48 + src/parser/route.h | 41 + src/parser/scanner.cxx | 3485 +++++++ src/parser/scanner.lxx | 333 + src/parser/sip_body.cpp | 326 + src/parser/sip_body.h | 260 + src/parser/sip_message.cpp | 476 + src/parser/sip_message.h | 270 + src/patterns/Makefile.am | 7 + src/patterns/observer.cpp | 53 + src/patterns/observer.h | 83 + src/phone.cpp | 3562 ++++++++ src/phone.h | 707 ++ src/phone_user.cpp | 1653 ++++ src/phone_user.h | 491 + src/presence/Makefile.am | 17 + src/presence/buddy.cpp | 574 ++ src/presence/buddy.h | 341 + src/presence/pidf_body.cpp | 221 + src/presence/pidf_body.h | 104 + src/presence/presence_dialog.cpp | 35 + src/presence/presence_dialog.h | 47 + src/presence/presence_epa.cpp | 71 + src/presence/presence_epa.h | 67 + src/presence/presence_state.cpp | 100 + src/presence/presence_state.h | 94 + src/presence/presence_subscription.cpp | 144 + src/presence/presence_subscription.h | 51 + src/prohibit_thread.cpp | 39 + src/prohibit_thread.h | 47 + src/protocol.h | 399 + src/redirect.cpp | 70 + src/redirect.h | 67 + src/sdp/Makefile.am | 27 + src/sdp/sdp.cpp | 806 ++ src/sdp/sdp.h | 295 + src/sdp/sdp_parse_ctrl.cpp | 70 + src/sdp/sdp_parse_ctrl.h | 63 + src/sdp/sdp_parser.h | 98 + src/sdp/sdp_parser.yxx | 294 + src/sdp/sdp_scanner.cxx | 1953 ++++ src/sdp/sdp_scanner.lxx | 95 + src/sender.cpp | 561 ++ src/sender.h | 33 + src/sequence_number.h | 148 + src/service.cpp | 400 + src/service.h | 98 + src/session.cpp | 822 ++ src/session.h | 211 + src/sockets/Makefile.am | 30 + src/sockets/connection.cpp | 294 + src/sockets/connection.h | 232 + src/sockets/connection_table.cpp | 411 + src/sockets/connection_table.h | 170 + src/sockets/dnssrv.cpp | 176 + src/sockets/dnssrv.h | 36 + src/sockets/interfaces.cpp | 114 + src/sockets/interfaces.h | 56 + src/sockets/socket.cpp | 444 + src/sockets/socket.h | 222 + src/sockets/url.cpp | 911 ++ src/sockets/url.h | 279 + src/stun/Makefile.am | 15 + src/stun/stun.cxx | 2587 ++++++ src/stun/stun.h | 405 + src/stun/stun_transaction.cpp | 680 ++ src/stun/stun_transaction.h | 159 + src/stun/udp.cxx | 349 + src/stun/udp.h | 156 + src/sub_refer.cpp | 324 + src/sub_refer.h | 68 + src/subscription.cpp | 695 ++ src/subscription.h | 319 + src/subscription_dialog.cpp | 409 + src/subscription_dialog.h | 170 + src/sys_settings.cpp | 2048 +++++ src/sys_settings.h | 548 ++ src/threads/Makefile.am | 11 + src/threads/mutex.cpp | 90 + src/threads/mutex.h | 86 + src/threads/sema.cpp | 76 + src/threads/sema.h | 44 + src/threads/thread.cpp | 108 + src/threads/thread.h | 59 + src/timekeeper.cpp | 785 ++ src/timekeeper.h | 267 + src/transaction.cpp | 1307 +++ src/transaction.h | 372 + src/transaction_layer.cpp | 315 + src/transaction_layer.h | 123 + src/transaction_mgr.cpp | 732 ++ src/transaction_mgr.h | 101 + src/translator.h | 49 + src/user.cpp | 3147 +++++++ src/user.h | 850 ++ src/userintf.cpp | 3511 +++++++ src/userintf.h | 446 + src/util.cpp | 765 ++ src/util.h | 273 + src/utils/Makefile.am | 11 + src/utils/file_utils.cpp | 128 + src/utils/file_utils.h | 82 + src/utils/mime_database.cpp | 104 + src/utils/mime_database.h | 90 + src/utils/record_file.h | 158 + src/utils/record_file.hpp | 178 + twinkle.desktop.in | 11 + twinkle.spec.in | 70 + 636 files changed, 187389 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 Doxyfile create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 THANKS create mode 100644 acinclude.m4 create mode 100644 configure.in create mode 100644 data/providers.csv create mode 100644 data/ringback.wav create mode 100644 data/ringtone.wav create mode 100644 m4/vl_lib_readline.m4 create mode 100644 sip.protocol create mode 100644 src/Makefile.am create mode 100644 src/abstract_dialog.cpp create mode 100644 src/abstract_dialog.h create mode 100644 src/address_book.cpp create mode 100644 src/address_book.h create mode 100644 src/audio/Makefile.am create mode 100644 src/audio/README_G711 create mode 100644 src/audio/audio_codecs.cpp create mode 100644 src/audio/audio_codecs.h create mode 100644 src/audio/audio_decoder.cpp create mode 100644 src/audio/audio_decoder.h create mode 100644 src/audio/audio_device.cpp create mode 100644 src/audio/audio_device.h create mode 100644 src/audio/audio_encoder.cpp create mode 100644 src/audio/audio_encoder.h create mode 100644 src/audio/audio_rx.cpp create mode 100644 src/audio/audio_rx.h create mode 100644 src/audio/audio_session.cpp create mode 100644 src/audio/audio_session.h create mode 100644 src/audio/audio_tx.cpp create mode 100644 src/audio/audio_tx.h create mode 100644 src/audio/dtmf_player.cpp create mode 100644 src/audio/dtmf_player.h create mode 100644 src/audio/freq_gen.cpp create mode 100644 src/audio/freq_gen.h create mode 100644 src/audio/g711.cpp create mode 100644 src/audio/g711.h create mode 100644 src/audio/g721.cpp create mode 100644 src/audio/g723_16.cpp create mode 100644 src/audio/g723_24.cpp create mode 100644 src/audio/g723_40.cpp create mode 100644 src/audio/g72x.cpp create mode 100644 src/audio/g72x.h create mode 100644 src/audio/gsm/COPYRIGHT create mode 100644 src/audio/gsm/ChangeLog create mode 100644 src/audio/gsm/INSTALL create mode 100644 src/audio/gsm/MACHINES create mode 100644 src/audio/gsm/Makefile.am create mode 100644 src/audio/gsm/README create mode 100644 src/audio/gsm/inc/config.h create mode 100644 src/audio/gsm/inc/gsm.h create mode 100644 src/audio/gsm/inc/private.h create mode 100644 src/audio/gsm/inc/proto.h create mode 100644 src/audio/gsm/inc/unproto.h create mode 100644 src/audio/gsm/src/add.cpp create mode 100644 src/audio/gsm/src/code.cpp create mode 100644 src/audio/gsm/src/debug.cpp create mode 100644 src/audio/gsm/src/decode.cpp create mode 100644 src/audio/gsm/src/gsm_create.cpp create mode 100644 src/audio/gsm/src/gsm_decode.cpp create mode 100644 src/audio/gsm/src/gsm_destroy.cpp create mode 100644 src/audio/gsm/src/gsm_encode.cpp create mode 100644 src/audio/gsm/src/gsm_explode.cpp create mode 100644 src/audio/gsm/src/gsm_implode.cpp create mode 100644 src/audio/gsm/src/gsm_option.cpp create mode 100644 src/audio/gsm/src/gsm_print.cpp create mode 100644 src/audio/gsm/src/long_term.cpp create mode 100644 src/audio/gsm/src/lpc.cpp create mode 100644 src/audio/gsm/src/preprocess.cpp create mode 100644 src/audio/gsm/src/rpe.cpp create mode 100644 src/audio/gsm/src/short_term.cpp create mode 100644 src/audio/gsm/src/table.cpp create mode 100644 src/audio/media_buffer.cpp create mode 100644 src/audio/media_buffer.h create mode 100644 src/audio/rtp_telephone_event.cpp create mode 100644 src/audio/rtp_telephone_event.h create mode 100644 src/audio/tone_gen.cpp create mode 100644 src/audio/tone_gen.h create mode 100644 src/audio/twinkle_rtp_session.cpp create mode 100644 src/audio/twinkle_rtp_session.h create mode 100644 src/audio/twinkle_zrtp_ui.cpp create mode 100644 src/audio/twinkle_zrtp_ui.h create mode 100644 src/audits/Makefile.am create mode 100644 src/audits/memman.cpp create mode 100644 src/audits/memman.h create mode 100644 src/auth.cpp create mode 100644 src/auth.h create mode 100644 src/call_history.cpp create mode 100644 src/call_history.h create mode 100644 src/call_script.cpp create mode 100644 src/call_script.h create mode 100644 src/client_request.cpp create mode 100644 src/client_request.h create mode 100644 src/cmd_socket.cpp create mode 100644 src/cmd_socket.h create mode 100644 src/dialog.cpp create mode 100644 src/dialog.h create mode 100644 src/diamondcard.cpp create mode 100644 src/diamondcard.h create mode 100644 src/epa.cpp create mode 100644 src/epa.h create mode 100644 src/events.cpp create mode 100644 src/events.h create mode 100644 src/exceptions.h create mode 100644 src/gui/address_finder.cpp create mode 100644 src/gui/address_finder.h create mode 100644 src/gui/addresscardform.ui create mode 100644 src/gui/addresscardform.ui.h create mode 100644 src/gui/addresslistviewitem.cpp create mode 100644 src/gui/addresslistviewitem.h create mode 100644 src/gui/authenticationform.ui create mode 100644 src/gui/authenticationform.ui.h create mode 100644 src/gui/buddyform.ui create mode 100644 src/gui/buddyform.ui.h create mode 100644 src/gui/buddylistview.cpp create mode 100644 src/gui/buddylistview.h create mode 100644 src/gui/command_args.h create mode 100644 src/gui/core_strings.h create mode 100644 src/gui/deregisterform.ui create mode 100644 src/gui/deregisterform.ui.h create mode 100644 src/gui/diamondcardprofileform.ui create mode 100644 src/gui/diamondcardprofileform.ui.h create mode 100644 src/gui/dtmfform.ui create mode 100644 src/gui/dtmfform.ui.h create mode 100644 src/gui/freedesksystray.cpp create mode 100644 src/gui/freedesksystray.h create mode 100644 src/gui/getaddressform.ui create mode 100644 src/gui/getaddressform.ui.h create mode 100644 src/gui/getprofilenameform.ui create mode 100644 src/gui/getprofilenameform.ui.h create mode 100644 src/gui/gui.cpp create mode 100644 src/gui/gui.h create mode 100644 src/gui/historyform.ui create mode 100644 src/gui/historyform.ui.h create mode 100644 src/gui/historylistview.cpp create mode 100644 src/gui/historylistview.h create mode 100644 src/gui/images/1downarrow.png create mode 100644 src/gui/images/1leftarrow-yellow.png create mode 100644 src/gui/images/1leftarrow.png create mode 100644 src/gui/images/1rightarrow.png create mode 100644 src/gui/images/1uparrow.png create mode 100644 src/gui/images/answer-disabled.png create mode 100644 src/gui/images/answer.png create mode 100644 src/gui/images/attach.png create mode 100644 src/gui/images/auto_answer-disabled.png create mode 100644 src/gui/images/auto_answer.png create mode 100644 src/gui/images/buddy.png create mode 100644 src/gui/images/bye-disabled.png create mode 100644 src/gui/images/bye.png create mode 100644 src/gui/images/cancel-disabled.png create mode 100644 src/gui/images/cancel.png create mode 100644 src/gui/images/cf-disabled.png create mode 100644 src/gui/images/cf.png create mode 100644 src/gui/images/clock.png create mode 100644 src/gui/images/conf-disabled.png create mode 100644 src/gui/images/conf.png create mode 100644 src/gui/images/conference-disabled.png create mode 100644 src/gui/images/conference.png create mode 100644 src/gui/images/consult-xfer.png create mode 100644 src/gui/images/contexthelp.png create mode 100644 src/gui/images/dtmf-0.png create mode 100644 src/gui/images/dtmf-1.png create mode 100644 src/gui/images/dtmf-2.png create mode 100644 src/gui/images/dtmf-3.png create mode 100644 src/gui/images/dtmf-4.png create mode 100644 src/gui/images/dtmf-5.png create mode 100644 src/gui/images/dtmf-6.png create mode 100644 src/gui/images/dtmf-7.png create mode 100644 src/gui/images/dtmf-8.png create mode 100644 src/gui/images/dtmf-9.png create mode 100644 src/gui/images/dtmf-a.png create mode 100644 src/gui/images/dtmf-b.png create mode 100644 src/gui/images/dtmf-c.png create mode 100644 src/gui/images/dtmf-d.png create mode 100644 src/gui/images/dtmf-disabled.png create mode 100644 src/gui/images/dtmf-pound.png create mode 100644 src/gui/images/dtmf-star.png create mode 100644 src/gui/images/dtmf.png create mode 100644 src/gui/images/edit.png create mode 100644 src/gui/images/edit16.png create mode 100644 src/gui/images/editcopy create mode 100644 src/gui/images/editcut create mode 100644 src/gui/images/editdelete.png create mode 100644 src/gui/images/editpaste create mode 100644 src/gui/images/encrypted-disabled.png create mode 100644 src/gui/images/encrypted.png create mode 100644 src/gui/images/encrypted32.png create mode 100644 src/gui/images/encrypted_verified.png create mode 100644 src/gui/images/exit.png create mode 100644 src/gui/images/favorites.png create mode 100644 src/gui/images/filenew create mode 100644 src/gui/images/fileopen-disabled.png create mode 100644 src/gui/images/fileopen.png create mode 100644 src/gui/images/filesave create mode 100644 src/gui/images/gear.png create mode 100644 src/gui/images/hold-disabled.png create mode 100644 src/gui/images/hold.png create mode 100644 src/gui/images/invite-disabled.png create mode 100644 src/gui/images/invite.png create mode 100644 src/gui/images/kcmpci.png create mode 100644 src/gui/images/kcmpci16.png create mode 100644 src/gui/images/kmix.png create mode 100644 src/gui/images/knotify.png create mode 100644 src/gui/images/kontact_contacts-disabled.png create mode 100644 src/gui/images/kontact_contacts.png create mode 100644 src/gui/images/kontact_contacts32.png create mode 100644 src/gui/images/log.png create mode 100644 src/gui/images/log_small.png create mode 100644 src/gui/images/message.png create mode 100644 src/gui/images/message32.png create mode 100644 src/gui/images/mime_application.png create mode 100644 src/gui/images/mime_audio.png create mode 100644 src/gui/images/mime_image.png create mode 100644 src/gui/images/mime_text.png create mode 100644 src/gui/images/mime_video.png create mode 100644 src/gui/images/missed-disabled.png create mode 100644 src/gui/images/missed.png create mode 100644 src/gui/images/mute-disabled.png create mode 100644 src/gui/images/mute.png create mode 100644 src/gui/images/mwi_failure16.png create mode 100644 src/gui/images/mwi_new16.png create mode 100644 src/gui/images/mwi_none.png create mode 100644 src/gui/images/mwi_none16.png create mode 100644 src/gui/images/mwi_none16_dis.png create mode 100644 src/gui/images/network.png create mode 100644 src/gui/images/no-indication.png create mode 100644 src/gui/images/ok.png create mode 100644 src/gui/images/package_network.png create mode 100644 src/gui/images/package_system.png create mode 100644 src/gui/images/password.png create mode 100644 src/gui/images/penguin-small.png create mode 100644 src/gui/images/penguin.png create mode 100644 src/gui/images/penguin_big.png create mode 100644 src/gui/images/presence.png create mode 100644 src/gui/images/presence_failed.png create mode 100644 src/gui/images/presence_offline.png create mode 100644 src/gui/images/presence_online.png create mode 100644 src/gui/images/presence_rejected.png create mode 100644 src/gui/images/presence_unknown.png create mode 100644 src/gui/images/print create mode 100644 src/gui/images/qt-logo.png create mode 100644 src/gui/images/redial-disabled.png create mode 100644 src/gui/images/redial.png create mode 100644 src/gui/images/redirect-disabled.png create mode 100644 src/gui/images/redirect.png create mode 100644 src/gui/images/redo create mode 100644 src/gui/images/reg-query.png create mode 100644 src/gui/images/reg_failed-disabled.png create mode 100644 src/gui/images/reg_failed.png create mode 100644 src/gui/images/reject-disabled.png create mode 100644 src/gui/images/reject.png create mode 100644 src/gui/images/save_as.png create mode 100644 src/gui/images/searchfind create mode 100644 src/gui/images/settings.png create mode 100644 src/gui/images/stat_conference.png create mode 100644 src/gui/images/stat_established.png create mode 100644 src/gui/images/stat_established_nomedia.png create mode 100644 src/gui/images/stat_mute.png create mode 100644 src/gui/images/stat_outgoing.png create mode 100644 src/gui/images/stat_ringing.png create mode 100644 src/gui/images/sys_auto_ans.png create mode 100644 src/gui/images/sys_auto_ans_dis.png create mode 100644 src/gui/images/sys_busy_estab.png create mode 100644 src/gui/images/sys_busy_estab_dis.png create mode 100644 src/gui/images/sys_busy_trans.png create mode 100644 src/gui/images/sys_busy_trans_dis.png create mode 100644 src/gui/images/sys_dnd.png create mode 100644 src/gui/images/sys_dnd_dis.png create mode 100644 src/gui/images/sys_encrypted.png create mode 100644 src/gui/images/sys_encrypted_dis.png create mode 100644 src/gui/images/sys_encrypted_verified.png create mode 100644 src/gui/images/sys_encrypted_verified_dis.png create mode 100644 src/gui/images/sys_hold.png create mode 100644 src/gui/images/sys_hold_dis.png create mode 100644 src/gui/images/sys_idle.png create mode 100644 src/gui/images/sys_idle_dis.png create mode 100644 src/gui/images/sys_missed.png create mode 100644 src/gui/images/sys_missed_dis.png create mode 100644 src/gui/images/sys_mute.png create mode 100644 src/gui/images/sys_mute_dis.png create mode 100644 src/gui/images/sys_mwi.png create mode 100644 src/gui/images/sys_mwi_dis.png create mode 100644 src/gui/images/sys_redir.png create mode 100644 src/gui/images/sys_redir_dis.png create mode 100644 src/gui/images/sys_services.png create mode 100644 src/gui/images/sys_services_dis.png create mode 100644 src/gui/images/telephone-hook.png create mode 100644 src/gui/images/transfer-disabled.png create mode 100644 src/gui/images/transfer.png create mode 100644 src/gui/images/twinkle16-disabled.png create mode 100644 src/gui/images/twinkle16.png create mode 100644 src/gui/images/twinkle24.png create mode 100644 src/gui/images/twinkle32.png create mode 100644 src/gui/images/twinkle48.png create mode 100644 src/gui/images/undo create mode 100644 src/gui/images/yast_PhoneTTOffhook.png create mode 100644 src/gui/images/yast_babelfish.png create mode 100644 src/gui/inviteform.ui create mode 100644 src/gui/inviteform.ui.h create mode 100644 src/gui/lang/Makefile.am create mode 100644 src/gui/lang/twinkle_cs.ts create mode 100644 src/gui/lang/twinkle_de.ts create mode 100644 src/gui/lang/twinkle_fr.ts create mode 100644 src/gui/lang/twinkle_nl.ts create mode 100644 src/gui/lang/twinkle_ru.ts create mode 100644 src/gui/lang/twinkle_sv.ts create mode 100644 src/gui/lang/twinkle_xx.ts create mode 100644 src/gui/logviewform.ui create mode 100644 src/gui/logviewform.ui.h create mode 100644 src/gui/main.cpp create mode 100644 src/gui/messageform.ui create mode 100644 src/gui/messageform.ui.h create mode 100644 src/gui/messageformview.cpp create mode 100644 src/gui/messageformview.h create mode 100644 src/gui/mphoneform.ui create mode 100644 src/gui/mphoneform.ui.h create mode 100644 src/gui/numberconversionform.ui create mode 100644 src/gui/numberconversionform.ui.h create mode 100644 src/gui/qt_translator.h create mode 100644 src/gui/redirectform.ui create mode 100644 src/gui/redirectform.ui.h create mode 100644 src/gui/selectnicform.ui create mode 100644 src/gui/selectnicform.ui.h create mode 100644 src/gui/selectprofileform.ui create mode 100644 src/gui/selectprofileform.ui.h create mode 100644 src/gui/selectuserform.ui create mode 100644 src/gui/selectuserform.ui.h create mode 100644 src/gui/sendfileform.ui create mode 100644 src/gui/sendfileform.ui.h create mode 100644 src/gui/srvredirectform.ui create mode 100644 src/gui/srvredirectform.ui.h create mode 100644 src/gui/syssettingsform.ui create mode 100644 src/gui/syssettingsform.ui.h create mode 100644 src/gui/termcapform.ui create mode 100644 src/gui/termcapform.ui.h create mode 100644 src/gui/textbrowsernoautolink.h create mode 100644 src/gui/transferform.ui create mode 100644 src/gui/transferform.ui.h create mode 100644 src/gui/twinkle.pro create mode 100644 src/gui/twinkleapplication.cpp create mode 100644 src/gui/twinkleapplication.h create mode 100644 src/gui/twinklesystray.cpp create mode 100644 src/gui/twinklesystray.h create mode 100644 src/gui/userprofileform.ui create mode 100644 src/gui/userprofileform.ui.h create mode 100644 src/gui/wizardform.ui create mode 100644 src/gui/wizardform.ui.h create mode 100644 src/gui/yesnodialog.cpp create mode 100644 src/gui/yesnodialog.h create mode 100644 src/id_object.cpp create mode 100644 src/id_object.h create mode 100644 src/im/Makefile.am create mode 100644 src/im/im_iscomposing_body.cpp create mode 100644 src/im/im_iscomposing_body.h create mode 100644 src/im/msg_session.cpp create mode 100644 src/im/msg_session.h create mode 100644 src/line.cpp create mode 100644 src/line.h create mode 100644 src/listener.cpp create mode 100644 src/listener.h create mode 100644 src/log.cpp create mode 100644 src/log.h create mode 100644 src/main.cpp create mode 100644 src/mwi/Makefile.am create mode 100644 src/mwi/mwi.cpp create mode 100644 src/mwi/mwi.h create mode 100644 src/mwi/mwi_dialog.cpp create mode 100644 src/mwi/mwi_dialog.h create mode 100644 src/mwi/mwi_subscription.cpp create mode 100644 src/mwi/mwi_subscription.h create mode 100644 src/mwi/simple_msg_sum_body.cpp create mode 100644 src/mwi/simple_msg_sum_body.h create mode 100644 src/parser/Makefile.am create mode 100644 src/parser/challenge.cpp create mode 100644 src/parser/challenge.h create mode 100644 src/parser/coding.cpp create mode 100644 src/parser/coding.h create mode 100644 src/parser/credentials.cpp create mode 100644 src/parser/credentials.h create mode 100644 src/parser/definitions.cpp create mode 100644 src/parser/definitions.h create mode 100644 src/parser/hdr_accept.cpp create mode 100644 src/parser/hdr_accept.h create mode 100644 src/parser/hdr_accept_encoding.cpp create mode 100644 src/parser/hdr_accept_encoding.h create mode 100644 src/parser/hdr_accept_language.cpp create mode 100644 src/parser/hdr_accept_language.h create mode 100644 src/parser/hdr_alert_info.cpp create mode 100644 src/parser/hdr_alert_info.h create mode 100644 src/parser/hdr_allow.cpp create mode 100644 src/parser/hdr_allow.h create mode 100644 src/parser/hdr_allow_events.cpp create mode 100644 src/parser/hdr_allow_events.h create mode 100644 src/parser/hdr_auth_info.cpp create mode 100644 src/parser/hdr_auth_info.h create mode 100644 src/parser/hdr_authorization.cpp create mode 100644 src/parser/hdr_authorization.h create mode 100644 src/parser/hdr_call_id.cpp create mode 100644 src/parser/hdr_call_id.h create mode 100644 src/parser/hdr_call_info.cpp create mode 100644 src/parser/hdr_call_info.h create mode 100644 src/parser/hdr_contact.cpp create mode 100644 src/parser/hdr_contact.h create mode 100644 src/parser/hdr_content_disp.cpp create mode 100644 src/parser/hdr_content_disp.h create mode 100644 src/parser/hdr_content_encoding.cpp create mode 100644 src/parser/hdr_content_encoding.h create mode 100644 src/parser/hdr_content_language.cpp create mode 100644 src/parser/hdr_content_language.h create mode 100644 src/parser/hdr_content_length.cpp create mode 100644 src/parser/hdr_content_length.h create mode 100644 src/parser/hdr_content_type.cpp create mode 100644 src/parser/hdr_content_type.h create mode 100644 src/parser/hdr_cseq.cpp create mode 100644 src/parser/hdr_cseq.h create mode 100644 src/parser/hdr_date.cpp create mode 100644 src/parser/hdr_date.h create mode 100644 src/parser/hdr_error_info.cpp create mode 100644 src/parser/hdr_error_info.h create mode 100644 src/parser/hdr_event.cpp create mode 100644 src/parser/hdr_event.h create mode 100644 src/parser/hdr_expires.cpp create mode 100644 src/parser/hdr_expires.h create mode 100644 src/parser/hdr_from.cpp create mode 100644 src/parser/hdr_from.h create mode 100644 src/parser/hdr_in_reply_to.cpp create mode 100644 src/parser/hdr_in_reply_to.h create mode 100644 src/parser/hdr_max_forwards.cpp create mode 100644 src/parser/hdr_max_forwards.h create mode 100644 src/parser/hdr_mime_version.cpp create mode 100644 src/parser/hdr_mime_version.h create mode 100644 src/parser/hdr_min_expires.cpp create mode 100644 src/parser/hdr_min_expires.h create mode 100644 src/parser/hdr_organization.cpp create mode 100644 src/parser/hdr_organization.h create mode 100644 src/parser/hdr_p_asserted_identity.cpp create mode 100644 src/parser/hdr_p_asserted_identity.h create mode 100644 src/parser/hdr_p_preferred_identity.cpp create mode 100644 src/parser/hdr_p_preferred_identity.h create mode 100644 src/parser/hdr_priority.cpp create mode 100644 src/parser/hdr_priority.h create mode 100644 src/parser/hdr_privacy.cpp create mode 100644 src/parser/hdr_privacy.h create mode 100644 src/parser/hdr_proxy_authenticate.cpp create mode 100644 src/parser/hdr_proxy_authenticate.h create mode 100644 src/parser/hdr_proxy_authorization.cpp create mode 100644 src/parser/hdr_proxy_authorization.h create mode 100644 src/parser/hdr_proxy_require.cpp create mode 100644 src/parser/hdr_proxy_require.h create mode 100644 src/parser/hdr_rack.cpp create mode 100644 src/parser/hdr_rack.h create mode 100644 src/parser/hdr_record_route.cpp create mode 100644 src/parser/hdr_record_route.h create mode 100644 src/parser/hdr_refer_sub.cpp create mode 100644 src/parser/hdr_refer_sub.h create mode 100644 src/parser/hdr_refer_to.cpp create mode 100644 src/parser/hdr_refer_to.h create mode 100644 src/parser/hdr_referred_by.cpp create mode 100644 src/parser/hdr_referred_by.h create mode 100644 src/parser/hdr_replaces.cpp create mode 100644 src/parser/hdr_replaces.h create mode 100644 src/parser/hdr_reply_to.cpp create mode 100644 src/parser/hdr_reply_to.h create mode 100644 src/parser/hdr_request_disposition.cpp create mode 100644 src/parser/hdr_request_disposition.h create mode 100644 src/parser/hdr_require.cpp create mode 100644 src/parser/hdr_require.h create mode 100644 src/parser/hdr_retry_after.cpp create mode 100644 src/parser/hdr_retry_after.h create mode 100644 src/parser/hdr_route.cpp create mode 100644 src/parser/hdr_route.h create mode 100644 src/parser/hdr_rseq.cpp create mode 100644 src/parser/hdr_rseq.h create mode 100644 src/parser/hdr_server.cpp create mode 100644 src/parser/hdr_server.h create mode 100644 src/parser/hdr_service_route.cpp create mode 100644 src/parser/hdr_service_route.h create mode 100644 src/parser/hdr_sip_etag.cpp create mode 100644 src/parser/hdr_sip_etag.h create mode 100644 src/parser/hdr_sip_if_match.cpp create mode 100644 src/parser/hdr_sip_if_match.h create mode 100644 src/parser/hdr_subject.cpp create mode 100644 src/parser/hdr_subject.h create mode 100644 src/parser/hdr_subscription_state.cpp create mode 100644 src/parser/hdr_subscription_state.h create mode 100644 src/parser/hdr_supported.cpp create mode 100644 src/parser/hdr_supported.h create mode 100644 src/parser/hdr_timestamp.cpp create mode 100644 src/parser/hdr_timestamp.h create mode 100644 src/parser/hdr_to.cpp create mode 100644 src/parser/hdr_to.h create mode 100644 src/parser/hdr_unsupported.cpp create mode 100644 src/parser/hdr_unsupported.h create mode 100644 src/parser/hdr_user_agent.cpp create mode 100644 src/parser/hdr_user_agent.h create mode 100644 src/parser/hdr_via.cpp create mode 100644 src/parser/hdr_via.h create mode 100644 src/parser/hdr_warning.cpp create mode 100644 src/parser/hdr_warning.h create mode 100644 src/parser/hdr_www_authenticate.cpp create mode 100644 src/parser/hdr_www_authenticate.h create mode 100644 src/parser/header.cpp create mode 100644 src/parser/header.h create mode 100644 src/parser/identity.cpp create mode 100644 src/parser/identity.h create mode 100644 src/parser/media_type.cpp create mode 100644 src/parser/media_type.h create mode 100644 src/parser/milenage.cpp create mode 100644 src/parser/milenage.h create mode 100644 src/parser/parameter.cpp create mode 100644 src/parser/parameter.h create mode 100644 src/parser/parse_ctrl.cpp create mode 100644 src/parser/parse_ctrl.h create mode 100644 src/parser/parser.cxx create mode 100644 src/parser/parser.h create mode 100644 src/parser/parser.yxx create mode 100644 src/parser/request.cpp create mode 100644 src/parser/request.h create mode 100755 src/parser/response.cpp create mode 100644 src/parser/response.h create mode 100644 src/parser/rijndael.cpp create mode 100644 src/parser/rijndael.h create mode 100644 src/parser/route.cpp create mode 100644 src/parser/route.h create mode 100644 src/parser/scanner.cxx create mode 100644 src/parser/scanner.lxx create mode 100644 src/parser/sip_body.cpp create mode 100644 src/parser/sip_body.h create mode 100644 src/parser/sip_message.cpp create mode 100644 src/parser/sip_message.h create mode 100644 src/patterns/Makefile.am create mode 100644 src/patterns/observer.cpp create mode 100644 src/patterns/observer.h create mode 100644 src/phone.cpp create mode 100644 src/phone.h create mode 100644 src/phone_user.cpp create mode 100644 src/phone_user.h create mode 100644 src/presence/Makefile.am create mode 100644 src/presence/buddy.cpp create mode 100644 src/presence/buddy.h create mode 100644 src/presence/pidf_body.cpp create mode 100644 src/presence/pidf_body.h create mode 100644 src/presence/presence_dialog.cpp create mode 100644 src/presence/presence_dialog.h create mode 100644 src/presence/presence_epa.cpp create mode 100644 src/presence/presence_epa.h create mode 100644 src/presence/presence_state.cpp create mode 100644 src/presence/presence_state.h create mode 100644 src/presence/presence_subscription.cpp create mode 100644 src/presence/presence_subscription.h create mode 100644 src/prohibit_thread.cpp create mode 100644 src/prohibit_thread.h create mode 100644 src/protocol.h create mode 100644 src/redirect.cpp create mode 100644 src/redirect.h create mode 100644 src/sdp/Makefile.am create mode 100644 src/sdp/sdp.cpp create mode 100644 src/sdp/sdp.h create mode 100644 src/sdp/sdp_parse_ctrl.cpp create mode 100644 src/sdp/sdp_parse_ctrl.h create mode 100644 src/sdp/sdp_parser.h create mode 100644 src/sdp/sdp_parser.yxx create mode 100644 src/sdp/sdp_scanner.cxx create mode 100644 src/sdp/sdp_scanner.lxx create mode 100644 src/sender.cpp create mode 100644 src/sender.h create mode 100644 src/sequence_number.h create mode 100644 src/service.cpp create mode 100644 src/service.h create mode 100644 src/session.cpp create mode 100644 src/session.h create mode 100644 src/sockets/Makefile.am create mode 100644 src/sockets/connection.cpp create mode 100644 src/sockets/connection.h create mode 100644 src/sockets/connection_table.cpp create mode 100644 src/sockets/connection_table.h create mode 100644 src/sockets/dnssrv.cpp create mode 100644 src/sockets/dnssrv.h create mode 100644 src/sockets/interfaces.cpp create mode 100644 src/sockets/interfaces.h create mode 100644 src/sockets/socket.cpp create mode 100644 src/sockets/socket.h create mode 100644 src/sockets/url.cpp create mode 100644 src/sockets/url.h create mode 100644 src/stun/Makefile.am create mode 100644 src/stun/stun.cxx create mode 100644 src/stun/stun.h create mode 100644 src/stun/stun_transaction.cpp create mode 100644 src/stun/stun_transaction.h create mode 100644 src/stun/udp.cxx create mode 100644 src/stun/udp.h create mode 100644 src/sub_refer.cpp create mode 100644 src/sub_refer.h create mode 100644 src/subscription.cpp create mode 100644 src/subscription.h create mode 100644 src/subscription_dialog.cpp create mode 100644 src/subscription_dialog.h create mode 100644 src/sys_settings.cpp create mode 100644 src/sys_settings.h create mode 100644 src/threads/Makefile.am create mode 100644 src/threads/mutex.cpp create mode 100644 src/threads/mutex.h create mode 100644 src/threads/sema.cpp create mode 100644 src/threads/sema.h create mode 100644 src/threads/thread.cpp create mode 100644 src/threads/thread.h create mode 100644 src/timekeeper.cpp create mode 100644 src/timekeeper.h create mode 100644 src/transaction.cpp create mode 100644 src/transaction.h create mode 100644 src/transaction_layer.cpp create mode 100644 src/transaction_layer.h create mode 100644 src/transaction_mgr.cpp create mode 100644 src/transaction_mgr.h create mode 100644 src/translator.h create mode 100644 src/user.cpp create mode 100644 src/user.h create mode 100644 src/userintf.cpp create mode 100644 src/userintf.h create mode 100644 src/util.cpp create mode 100644 src/util.h create mode 100644 src/utils/Makefile.am create mode 100644 src/utils/file_utils.cpp create mode 100644 src/utils/file_utils.h create mode 100644 src/utils/mime_database.cpp create mode 100644 src/utils/mime_database.h create mode 100644 src/utils/record_file.h create mode 100644 src/utils/record_file.hpp create mode 100644 twinkle.desktop.in create mode 100644 twinkle.spec.in diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea53863 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# generated by autoreconf -vif +Makefile.in +/INSTALL +/aclocal.m4 +/autom4te.cache/ +/config.guess +/config.sub +/configure +/depcomp +/install-sh +/missing +/ylwrap +src/twinkle_config.h.in + +# generated by ./configure +Makefile +.deps +/config.log +/config.status +/qtccxxincl.pro +/twinkle.spec +src/stamp-h1 +src/twinkle_config.h + +# generated by build +*.o +*.a +*.qm +/twinkle.desktop +src/twinkle +src/gui/.moc/ +src/gui/.ui/ +src/gui/twinkle +src/sdp/sdp_parser.cxx +src/sdp/sdp_parser.hxx + +# generic +*~ + diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..21ab903 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,35 @@ +Author of Twinkle: + +Michel de Boer designed and implemented Twinkle. + +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 diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d60c31a --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library General +Public License instead of this License. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..c8916bb --- /dev/null +++ b/ChangeLog @@ -0,0 +1,796 @@ +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 succesfully 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 diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..96998b0 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,1252 @@ +# 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 diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..5c3bcac --- /dev/null +++ b/Makefile.am @@ -0,0 +1,31 @@ +# The Makefile for the Qt-part of the program is created by qmake. +# That Makefile builts the executable. +# So here the executable is defined as a script such that it will +# only be installed and not built. + +ACLOCAL_AMFLAGS = -I m4 + +pkglangdir = $(pkgdatadir)/lang + +bin_SCRIPTS = src/gui/twinkle +noinst_SCRIPTS = twinkle.desktop + +pkgdata_DATA = data/ringback.wav data/ringtone.wav \ + data/providers.csv \ + src/gui/images/twinkle16.png src/gui/images/twinkle32.png \ + src/gui/images/twinkle48.png + +EXTRA_DIST = src/gui/*.ui src/gui/*.h src/gui/*.cpp src/gui/Makefile \ + src/gui/twinkle.pro src/gui/images/* \ + data/ringback.wav data/ringtone.wav data/providers.csv \ + m4/*.m4 \ + sip.protocol twinkle.desktop.in Doxyfile + +SUBDIRS = src + +edit = sed \ + -e 's,@datadir\@,$(pkgdatadir),g' \ + -e 's,@prefix\@,$(prefix),g' + +twinkle.desktop: Makefile $(srcdir)/twinkle.desktop.in + $(edit) $(srcdir)/twinkle.desktop.in >twinkle.desktop diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..c8916bb --- /dev/null +++ b/NEWS @@ -0,0 +1,796 @@ +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 succesfully 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 diff --git a/README b/README new file mode 100644 index 0000000..85fe9b4 --- /dev/null +++ b/README @@ -0,0 +1,253 @@ +Twinkle is a SIP based VoIP client. + +Release 0.5 notes +----------------- +In this release the SIP UDP port and RTP port settings have been +moved from the user profile to the system settings. If you made +any changes to the default port values in your user profiles, then +these changes will be lost. + +Library requirements +-------------------- +To compile Twinkle you need the following libraries: + +libccext2 (version >= 1.4.2) [GNU Common C++] +libccgnu2 (version >= 1.4.2) [GNU Common C++] + http://www.gnu.org/software/commoncpp/ + +libccrtp1 (version >= 1.5.0) [GNU RTP Stack] +libzrtpcpp (version >= 0.9.0) [Extension library of GNU ccRTP] + http://www.gnu.org/software/ccrtp/ + +libqt-mt (version >= 3.3.0) [Qt library with threading support] + http://www.trolltech.com/ + For the Qt environment the $QTDIR variable must be set + correctly + +Shared user data +---------------- +Installation will create the following directory for shared user data +on your system: + + $(pkgdatadir)/twinkle + +Typical value for pkgdatadir is: /usr/local/share or /opt/kde3/share + +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 directory ".twinkle" 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 This is the latest log file + twinkle.log.old This is the 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 minumimum 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. + +NOTE: Twinkle does not support DNS SRV records yet. + +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. + +January 2006 + +Michel de Boer +michel@twinklephone.com diff --git a/THANKS b/THANKS new file mode 100644 index 0000000..62d03ff --- /dev/null +++ b/THANKS @@ -0,0 +1,16 @@ +Thanks 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. diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 0000000..8257f28 --- /dev/null +++ b/acinclude.m4 @@ -0,0 +1,11630 @@ +## -*- autoconf -*- + +dnl This file is part of the KDE libraries/packages +dnl Copyright (C) 1997 Janos Farkas (chexum@shadow.banki.hu) +dnl (C) 1997,98,99 Stephan Kulow (coolo@kde.org) + +dnl This file is free software; you can redistribute it and/or +dnl modify it under the terms of the GNU Library General Public +dnl License as published by the Free Software Foundation; either +dnl version 2 of the License, or (at your option) any later version. + +dnl This library is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +dnl Library General Public License for more details. + +dnl You should have received a copy of the GNU Library General Public License +dnl along with this library; see the file COPYING.LIB. If not, write to +dnl the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +dnl Boston, MA 02111-1307, USA. + +dnl IMPORTANT NOTE: +dnl Please do not modify this file unless you expect your modifications to be +dnl carried into every other module in the repository. +dnl +dnl Single-module modifications are best placed in configure.in for kdelibs +dnl and kdebase or configure.in.in if present. + +# KDE_PATH_X_DIRECT +dnl Internal subroutine of AC_PATH_X. +dnl Set ac_x_includes and/or ac_x_libraries. +AC_DEFUN([KDE_PATH_X_DIRECT], +[ +AC_REQUIRE([KDE_CHECK_LIB64]) + +if test "$ac_x_includes" = NO; then + # Guess where to find include files, by looking for this one X11 .h file. + test -z "$x_direct_test_include" && x_direct_test_include=X11/Intrinsic.h + + # First, try using that file with no special directory specified. +AC_TRY_CPP([#include <$x_direct_test_include>], +[# We can compile using X headers with no special include directory. +ac_x_includes=], +[# Look for the header file in a standard set of common directories. +# Check X11 before X11Rn because it is often a symlink to the current release. + for ac_dir in \ + /usr/X11/include \ + /usr/X11R6/include \ + /usr/X11R5/include \ + /usr/X11R4/include \ + \ + /usr/include/X11 \ + /usr/include/X11R6 \ + /usr/include/X11R5 \ + /usr/include/X11R4 \ + \ + /usr/local/X11/include \ + /usr/local/X11R6/include \ + /usr/local/X11R5/include \ + /usr/local/X11R4/include \ + \ + /usr/local/include/X11 \ + /usr/local/include/X11R6 \ + /usr/local/include/X11R5 \ + /usr/local/include/X11R4 \ + \ + /usr/X386/include \ + /usr/x386/include \ + /usr/XFree86/include/X11 \ + \ + /usr/include \ + /usr/local/include \ + /usr/unsupported/include \ + /usr/athena/include \ + /usr/local/x11r5/include \ + /usr/lpp/Xamples/include \ + \ + /usr/openwin/include \ + /usr/openwin/share/include \ + ; \ + do + if test -r "$ac_dir/$x_direct_test_include"; then + ac_x_includes=$ac_dir + break + fi + done]) +fi # $ac_x_includes = NO + +if test "$ac_x_libraries" = NO; then + # Check for the libraries. + + test -z "$x_direct_test_library" && x_direct_test_library=Xt + test -z "$x_direct_test_function" && x_direct_test_function=XtMalloc + + # See if we find them without any special options. + # Don't add to $LIBS permanently. + ac_save_LIBS="$LIBS" + LIBS="-l$x_direct_test_library $LIBS" +AC_TRY_LINK([#include ], [${x_direct_test_function}(1)], +[LIBS="$ac_save_LIBS" +# We can link X programs with no special library path. +ac_x_libraries=], +[LIBS="$ac_save_LIBS" +# First see if replacing the include by lib works. +# Check X11 before X11Rn because it is often a symlink to the current release. +for ac_dir in `echo "$ac_x_includes" | sed s/include/lib${kdelibsuff}/` \ + /usr/X11/lib${kdelibsuff} \ + /usr/X11R6/lib${kdelibsuff} \ + /usr/X11R5/lib${kdelibsuff} \ + /usr/X11R4/lib${kdelibsuff} \ + \ + /usr/lib${kdelibsuff}/X11 \ + /usr/lib${kdelibsuff}/X11R6 \ + /usr/lib${kdelibsuff}/X11R5 \ + /usr/lib${kdelibsuff}/X11R4 \ + \ + /usr/local/X11/lib${kdelibsuff} \ + /usr/local/X11R6/lib${kdelibsuff} \ + /usr/local/X11R5/lib${kdelibsuff} \ + /usr/local/X11R4/lib${kdelibsuff} \ + \ + /usr/local/lib${kdelibsuff}/X11 \ + /usr/local/lib${kdelibsuff}/X11R6 \ + /usr/local/lib${kdelibsuff}/X11R5 \ + /usr/local/lib${kdelibsuff}/X11R4 \ + \ + /usr/X386/lib${kdelibsuff} \ + /usr/x386/lib${kdelibsuff} \ + /usr/XFree86/lib${kdelibsuff}/X11 \ + \ + /usr/lib${kdelibsuff} \ + /usr/local/lib${kdelibsuff} \ + /usr/unsupported/lib${kdelibsuff} \ + /usr/athena/lib${kdelibsuff} \ + /usr/local/x11r5/lib${kdelibsuff} \ + /usr/lpp/Xamples/lib${kdelibsuff} \ + /lib/usr/lib${kdelibsuff}/X11 \ + \ + /usr/openwin/lib${kdelibsuff} \ + /usr/openwin/share/lib${kdelibsuff} \ + ; \ +do +dnl Don't even attempt the hair of trying to link an X program! + for ac_extension in a so sl; do + if test -r $ac_dir/lib${x_direct_test_library}.$ac_extension; then + ac_x_libraries=$ac_dir + break 2 + fi + done +done]) +fi # $ac_x_libraries = NO +]) + + +dnl ------------------------------------------------------------------------ +dnl Find a file (or one of more files in a list of dirs) +dnl ------------------------------------------------------------------------ +dnl +AC_DEFUN([AC_FIND_FILE], +[ +$3=NO +for i in $2; +do + for j in $1; + do + echo "configure: __oline__: $i/$j" >&AC_FD_CC + if test -r "$i/$j"; then + echo "taking that" >&AC_FD_CC + $3=$i + break 2 + fi + done +done +]) + +dnl KDE_FIND_PATH(program-name, variable-name, list-of-dirs, +dnl if-not-found, test-parameter, prepend-path) +dnl +dnl Look for program-name in list-of-dirs+$PATH. +dnl If prepend-path is set, look in $PATH+list-of-dirs instead. +dnl If found, $variable-name is set. If not, if-not-found is evaluated. +dnl test-parameter: if set, the program is executed with this arg, +dnl and only a successful exit code is required. +AC_DEFUN([KDE_FIND_PATH], +[ + AC_MSG_CHECKING([for $1]) + if test -n "$$2"; then + kde_cv_path="$$2"; + else + kde_cache=`echo $1 | sed 'y%./+-%__p_%'` + + AC_CACHE_VAL(kde_cv_path_$kde_cache, + [ + kde_cv_path="NONE" + kde_save_IFS=$IFS + IFS=':' + dirs="" + for dir in $PATH; do + dirs="$dirs $dir" + done + if test -z "$6"; then dnl Append dirs in PATH (default) + dirs="$3 $dirs" + else dnl Prepend dirs in PATH (if 6th arg is set) + dirs="$dirs $3" + fi + IFS=$kde_save_IFS + + for dir in $dirs; do + if test -x "$dir/$1"; then + if test -n "$5" + then + evalstr="$dir/$1 $5 2>&1 " + if eval $evalstr; then + kde_cv_path="$dir/$1" + break + fi + else + kde_cv_path="$dir/$1" + break + fi + fi + done + + eval "kde_cv_path_$kde_cache=$kde_cv_path" + + ]) + + eval "kde_cv_path=\"`echo '$kde_cv_path_'$kde_cache`\"" + + fi + + if test -z "$kde_cv_path" || test "$kde_cv_path" = NONE; then + AC_MSG_RESULT(not found) + $4 + else + AC_MSG_RESULT($kde_cv_path) + $2=$kde_cv_path + + fi +]) + +AC_DEFUN([KDE_MOC_ERROR_MESSAGE], +[ + AC_MSG_ERROR([No Qt meta object compiler (moc) found! +Please check whether you installed Qt correctly. +You need to have a running moc binary. +configure tried to run $ac_cv_path_moc and the test didn't +succeed. If configure shouldn't have tried this one, set +the environment variable MOC to the right one before running +configure. +]) +]) + +AC_DEFUN([KDE_UIC_ERROR_MESSAGE], +[ + AC_MSG_WARN([No Qt ui compiler (uic) found! +Please check whether you installed Qt correctly. +You need to have a running uic binary. +configure tried to run $ac_cv_path_uic and the test didn't +succeed. If configure shouldn't have tried this one, set +the environment variable UIC to the right one before running +configure. +]) +]) + + +AC_DEFUN([KDE_CHECK_UIC_FLAG], +[ + AC_MSG_CHECKING([whether uic supports -$1 ]) + kde_cache=`echo $1 | sed 'y% .=/+-%____p_%'` + AC_CACHE_VAL(kde_cv_prog_uic_$kde_cache, + [ + cat >conftest.ui < +EOT + ac_uic_testrun="$UIC_PATH -$1 $2 conftest.ui >/dev/null" + if AC_TRY_EVAL(ac_uic_testrun); then + eval "kde_cv_prog_uic_$kde_cache=yes" + else + eval "kde_cv_prog_uic_$kde_cache=no" + fi + rm -f conftest* + ]) + + if eval "test \"`echo '$kde_cv_prog_uic_'$kde_cache`\" = yes"; then + AC_MSG_RESULT([yes]) + : + $3 + else + AC_MSG_RESULT([no]) + : + $4 + fi +]) + + +dnl ------------------------------------------------------------------------ +dnl Find the meta object compiler and the ui compiler in the PATH, +dnl in $QTDIR/bin, and some more usual places +dnl ------------------------------------------------------------------------ +dnl +AC_DEFUN([AC_PATH_QT_MOC_UIC], +[ + AC_REQUIRE([KDE_CHECK_PERL]) + qt_bindirs="" + for dir in $kde_qt_dirs; do + qt_bindirs="$qt_bindirs $dir/bin $dir/src/moc" + done + qt_bindirs="$qt_bindirs /usr/bin /usr/X11R6/bin /usr/local/qt/bin" + if test ! "$ac_qt_bindir" = "NO"; then + qt_bindirs="$ac_qt_bindir $qt_bindirs" + fi + + KDE_FIND_PATH(moc, MOC, [$qt_bindirs], [KDE_MOC_ERROR_MESSAGE]) + if test -z "$UIC_NOT_NEEDED"; then + KDE_FIND_PATH(uic, UIC_PATH, [$qt_bindirs], [UIC_PATH=""]) + if test -z "$UIC_PATH" ; then + KDE_UIC_ERROR_MESSAGE + exit 1 + else + UIC=$UIC_PATH + + if test $kde_qtver = 3; then + KDE_CHECK_UIC_FLAG(L,[/nonexistent],ac_uic_supports_libpath=yes,ac_uic_supports_libpath=no) + KDE_CHECK_UIC_FLAG(nounload,,ac_uic_supports_nounload=yes,ac_uic_supports_nounload=no) + + if test x$ac_uic_supports_libpath = xyes; then + UIC="$UIC -L \$(kde_widgetdir)" + fi + if test x$ac_uic_supports_nounload = xyes; then + UIC="$UIC -nounload" + fi + fi + fi + else + UIC="echo uic not available: " + fi + + AC_SUBST(MOC) + AC_SUBST(UIC) + + UIC_TR="i18n" + if test $kde_qtver = 3; then + UIC_TR="tr2i18n" + fi + + AC_SUBST(UIC_TR) +]) + +AC_DEFUN([KDE_1_CHECK_PATHS], +[ + KDE_1_CHECK_PATH_HEADERS + + KDE_TEST_RPATH= + + if test -n "$USE_RPATH"; then + + if test -n "$kde_libraries"; then + KDE_TEST_RPATH="-R $kde_libraries" + fi + + if test -n "$qt_libraries"; then + KDE_TEST_RPATH="$KDE_TEST_RPATH -R $qt_libraries" + fi + + if test -n "$x_libraries"; then + KDE_TEST_RPATH="$KDE_TEST_RPATH -R $x_libraries" + fi + + KDE_TEST_RPATH="$KDE_TEST_RPATH $KDE_EXTRA_RPATH" + fi + +AC_MSG_CHECKING([for KDE libraries installed]) +ac_link='$LIBTOOL_SHELL --silent --mode=link ${CXX-g++} -o conftest $CXXFLAGS $all_includes $CPPFLAGS $LDFLAGS $all_libraries conftest.$ac_ext $LIBS -lkdecore $LIBQT $KDE_TEST_RPATH 1>&5' + +if AC_TRY_EVAL(ac_link) && test -s conftest; then + AC_MSG_RESULT(yes) +else + AC_MSG_ERROR([your system fails at linking a small KDE application! +Check, if your compiler is installed correctly and if you have used the +same compiler to compile Qt and kdelibs as you did use now. +For more details about this problem, look at the end of config.log.]) +fi + +if eval `KDEDIR= ./conftest 2>&5`; then + kde_result=done +else + kde_result=problems +fi + +KDEDIR= ./conftest 2> /dev/null >&5 # make an echo for config.log +kde_have_all_paths=yes + +KDE_SET_PATHS($kde_result) + +]) + +AC_DEFUN([KDE_SET_PATHS], +[ + kde_cv_all_paths="kde_have_all_paths=\"yes\" \ + kde_htmldir=\"$kde_htmldir\" \ + kde_appsdir=\"$kde_appsdir\" \ + kde_icondir=\"$kde_icondir\" \ + kde_sounddir=\"$kde_sounddir\" \ + kde_datadir=\"$kde_datadir\" \ + kde_locale=\"$kde_locale\" \ + kde_cgidir=\"$kde_cgidir\" \ + kde_confdir=\"$kde_confdir\" \ + kde_kcfgdir=\"$kde_kcfgdir\" \ + kde_mimedir=\"$kde_mimedir\" \ + kde_toolbardir=\"$kde_toolbardir\" \ + kde_wallpaperdir=\"$kde_wallpaperdir\" \ + kde_templatesdir=\"$kde_templatesdir\" \ + kde_bindir=\"$kde_bindir\" \ + kde_servicesdir=\"$kde_servicesdir\" \ + kde_servicetypesdir=\"$kde_servicetypesdir\" \ + kde_moduledir=\"$kde_moduledir\" \ + kde_styledir=\"$kde_styledir\" \ + kde_widgetdir=\"$kde_widgetdir\" \ + xdg_appsdir=\"$xdg_appsdir\" \ + xdg_menudir=\"$xdg_menudir\" \ + xdg_directorydir=\"$xdg_directorydir\" \ + kde_result=$1" +]) + +AC_DEFUN([KDE_SET_DEFAULT_PATHS], +[ +if test "$1" = "default"; then + + if test -z "$kde_htmldir"; then + kde_htmldir='\${datadir}/doc/HTML' + fi + if test -z "$kde_appsdir"; then + kde_appsdir='\${datadir}/applnk' + fi + if test -z "$kde_icondir"; then + kde_icondir='\${datadir}/icons' + fi + if test -z "$kde_sounddir"; then + kde_sounddir='\${datadir}/sounds' + fi + if test -z "$kde_datadir"; then + kde_datadir='\${datadir}/apps' + fi + if test -z "$kde_locale"; then + kde_locale='\${datadir}/locale' + fi + if test -z "$kde_cgidir"; then + kde_cgidir='\${exec_prefix}/cgi-bin' + fi + if test -z "$kde_confdir"; then + kde_confdir='\${datadir}/config' + fi + if test -z "$kde_kcfgdir"; then + kde_kcfgdir='\${datadir}/config.kcfg' + fi + if test -z "$kde_mimedir"; then + kde_mimedir='\${datadir}/mimelnk' + fi + if test -z "$kde_toolbardir"; then + kde_toolbardir='\${datadir}/toolbar' + fi + if test -z "$kde_wallpaperdir"; then + kde_wallpaperdir='\${datadir}/wallpapers' + fi + if test -z "$kde_templatesdir"; then + kde_templatesdir='\${datadir}/templates' + fi + if test -z "$kde_bindir"; then + kde_bindir='\${exec_prefix}/bin' + fi + if test -z "$kde_servicesdir"; then + kde_servicesdir='\${datadir}/services' + fi + if test -z "$kde_servicetypesdir"; then + kde_servicetypesdir='\${datadir}/servicetypes' + fi + if test -z "$kde_moduledir"; then + if test "$kde_qtver" = "2"; then + kde_moduledir='\${libdir}/kde2' + else + kde_moduledir='\${libdir}/kde3' + fi + fi + if test -z "$kde_styledir"; then + kde_styledir='\${libdir}/kde3/plugins/styles' + fi + if test -z "$kde_widgetdir"; then + kde_widgetdir='\${libdir}/kde3/plugins/designer' + fi + if test -z "$xdg_appsdir"; then + xdg_appsdir='\${datadir}/applications/kde' + fi + if test -z "$xdg_menudir"; then + xdg_menudir='\${sysconfdir}/xdg/menus' + fi + if test -z "$xdg_directorydir"; then + xdg_directorydir='\${datadir}/desktop-directories' + fi + + KDE_SET_PATHS(defaults) + +else + + if test $kde_qtver = 1; then + AC_MSG_RESULT([compiling]) + KDE_1_CHECK_PATHS + else + AC_MSG_ERROR([path checking not yet supported for KDE 2]) + fi + +fi +]) + +AC_DEFUN([KDE_CHECK_PATHS_FOR_COMPLETENESS], +[ if test -z "$kde_htmldir" || test -z "$kde_appsdir" || + test -z "$kde_icondir" || test -z "$kde_sounddir" || + test -z "$kde_datadir" || test -z "$kde_locale" || + test -z "$kde_cgidir" || test -z "$kde_confdir" || + test -z "$kde_kcfgdir" || + test -z "$kde_mimedir" || test -z "$kde_toolbardir" || + test -z "$kde_wallpaperdir" || test -z "$kde_templatesdir" || + test -z "$kde_bindir" || test -z "$kde_servicesdir" || + test -z "$kde_servicetypesdir" || test -z "$kde_moduledir" || + test -z "$kde_styledir" || test -z "kde_widgetdir" || + test -z "$xdg_appsdir" || test -z "$xdg_menudir" || test -z "$xdg_directorydir" || + test "x$kde_have_all_paths" != "xyes"; then + kde_have_all_paths=no + fi +]) + +AC_DEFUN([KDE_MISSING_PROG_ERROR], +[ + AC_MSG_ERROR([The important program $1 was not found! +Please check whether you installed KDE correctly. +]) +]) + +AC_DEFUN([KDE_MISSING_ARTS_ERROR], +[ + AC_MSG_ERROR([The important program $1 was not found! +Please check whether you installed aRts correctly or use +--without-arts to compile without aRts support (this will remove functionality). +]) +]) + +AC_DEFUN([KDE_SET_DEFAULT_BINDIRS], +[ + kde_default_bindirs="/usr/bin /usr/local/bin /opt/local/bin /usr/X11R6/bin /opt/kde/bin /opt/kde3/bin /usr/kde/bin /usr/local/kde/bin" + test -n "$KDEDIR" && kde_default_bindirs="$KDEDIR/bin $kde_default_bindirs" + if test -n "$KDEDIRS"; then + kde_save_IFS=$IFS + IFS=: + for dir in $KDEDIRS; do + kde_default_bindirs="$dir/bin $kde_default_bindirs " + done + IFS=$kde_save_IFS + fi +]) + +AC_DEFUN([KDE_SUBST_PROGRAMS], +[ + AC_ARG_WITH(arts, + AC_HELP_STRING([--without-arts],[build without aRts [default=no]]), + [build_arts=$withval], + [build_arts=yes] + ) + AM_CONDITIONAL(include_ARTS, test "$build_arts" '!=' "no") + if test "$build_arts" = "no"; then + AC_DEFINE(WITHOUT_ARTS, 1, [Defined if compiling without arts]) + fi + + KDE_SET_DEFAULT_BINDIRS + kde_default_bindirs="$exec_prefix/bin $prefix/bin $kde_libs_prefix/bin $kde_default_bindirs" + KDE_FIND_PATH(dcopidl, DCOPIDL, [$kde_default_bindirs], [KDE_MISSING_PROG_ERROR(dcopidl)]) + KDE_FIND_PATH(dcopidl2cpp, DCOPIDL2CPP, [$kde_default_bindirs], [KDE_MISSING_PROG_ERROR(dcopidl2cpp)]) + if test "$build_arts" '!=' "no"; then + KDE_FIND_PATH(mcopidl, MCOPIDL, [$kde_default_bindirs], [KDE_MISSING_ARTS_ERROR(mcopidl)]) + KDE_FIND_PATH(artsc-config, ARTSCCONFIG, [$kde_default_bindirs], [KDE_MISSING_ARTS_ERROR(artsc-config)]) + fi + KDE_FIND_PATH(meinproc, MEINPROC, [$kde_default_bindirs]) + + kde32ornewer=1 + kde33ornewer=1 + if test -n "$kde_qtver" && test "$kde_qtver" -lt 3; then + kde32ornewer= + kde33ornewer= + else + if test "$kde_qtver" = "3"; then + if test "$kde_qtsubver" -le 1; then + kde32ornewer= + fi + if test "$kde_qtsubver" -le 2; then + kde33ornewer= + fi + fi + fi + + if test -n "$kde32ornewer"; then + KDE_FIND_PATH(kconfig_compiler, KCONFIG_COMPILER, [$kde_default_bindirs], [KDE_MISSING_PROG_ERROR(kconfig_compiler)]) + KDE_FIND_PATH(dcopidlng, DCOPIDLNG, [$kde_default_bindirs], [KDE_MISSING_PROG_ERROR(dcopidlng)]) + fi + if test -n "$kde33ornewer"; then + KDE_FIND_PATH(makekdewidgets, MAKEKDEWIDGETS, [$kde_default_bindirs], [KDE_MISSING_PROG_ERROR(makekdewidgets)]) + AC_SUBST(MAKEKDEWIDGETS) + fi + KDE_FIND_PATH(xmllint, XMLLINT, [${prefix}/bin ${exec_prefix}/bin], [XMLLINT=""]) + + if test -n "$MEINPROC" && test ! "$MEINPROC" = "compiled"; then + kde_sharedirs="/usr/share/kde /usr/local/share /usr/share /opt/kde3/share /opt/kde/share $prefix/share" + test -n "$KDEDIR" && kde_sharedirs="$KDEDIR/share $kde_sharedirs" + AC_FIND_FILE(apps/ksgmltools2/customization/kde-chunk.xsl, $kde_sharedirs, KDE_XSL_STYLESHEET) + if test "$KDE_XSL_STYLESHEET" = "NO"; then + KDE_XSL_STYLESHEET="" + else + KDE_XSL_STYLESHEET="$KDE_XSL_STYLESHEET/apps/ksgmltools2/customization/kde-chunk.xsl" + fi + fi + + DCOP_DEPENDENCIES='$(DCOPIDL)' + if test -n "$kde32ornewer"; then + KCFG_DEPENDENCIES='$(KCONFIG_COMPILER)' + DCOP_DEPENDENCIES='$(DCOPIDL) $(DCOPIDLNG)' + AC_SUBST(KCONFIG_COMPILER) + AC_SUBST(KCFG_DEPENDENCIES) + AC_SUBST(DCOPIDLNG) + fi + AC_SUBST(DCOPIDL) + AC_SUBST(DCOPIDL2CPP) + AC_SUBST(DCOP_DEPENDENCIES) + AC_SUBST(MCOPIDL) + AC_SUBST(ARTSCCONFIG) + AC_SUBST(MEINPROC) + AC_SUBST(KDE_XSL_STYLESHEET) + AC_SUBST(XMLLINT) +])dnl + +AC_DEFUN([AC_CREATE_KFSSTND], +[ +AC_REQUIRE([AC_CHECK_RPATH]) + +AC_MSG_CHECKING([for KDE paths]) +kde_result="" +kde_cached_paths=yes +AC_CACHE_VAL(kde_cv_all_paths, +[ + KDE_SET_DEFAULT_PATHS($1) + kde_cached_paths=no +]) +eval "$kde_cv_all_paths" +KDE_CHECK_PATHS_FOR_COMPLETENESS +if test "$kde_have_all_paths" = "no" && test "$kde_cached_paths" = "yes"; then + # wrong values were cached, may be, we can set better ones + kde_result= + kde_htmldir= kde_appsdir= kde_icondir= kde_sounddir= + kde_datadir= kde_locale= kde_cgidir= kde_confdir= kde_kcfgdir= + kde_mimedir= kde_toolbardir= kde_wallpaperdir= kde_templatesdir= + kde_bindir= kde_servicesdir= kde_servicetypesdir= kde_moduledir= + kde_have_all_paths= + kde_styledir= + kde_widgetdir= + xdg_appsdir = xdg_menudir= xdg_directorydir= + KDE_SET_DEFAULT_PATHS($1) + eval "$kde_cv_all_paths" + KDE_CHECK_PATHS_FOR_COMPLETENESS + kde_result="$kde_result (cache overridden)" +fi +if test "$kde_have_all_paths" = "no"; then + AC_MSG_ERROR([configure could not run a little KDE program to test the environment. +Since it had compiled and linked before, it must be a strange problem on your system. +Look at config.log for details. If you are not able to fix this, look at +http://www.kde.org/faq/installation.html or any www.kde.org mirror. +(If you're using an egcs version on Linux, you may update binutils!) +]) +else + rm -f conftest* + AC_MSG_RESULT($kde_result) +fi + +bindir=$kde_bindir + +KDE_SUBST_PROGRAMS + +]) + +AC_DEFUN([AC_SUBST_KFSSTND], +[ +AC_SUBST(kde_htmldir) +AC_SUBST(kde_appsdir) +AC_SUBST(kde_icondir) +AC_SUBST(kde_sounddir) +AC_SUBST(kde_datadir) +AC_SUBST(kde_locale) +AC_SUBST(kde_confdir) +AC_SUBST(kde_kcfgdir) +AC_SUBST(kde_mimedir) +AC_SUBST(kde_wallpaperdir) +AC_SUBST(kde_bindir) +dnl X Desktop Group standards +AC_SUBST(xdg_appsdir) +AC_SUBST(xdg_menudir) +AC_SUBST(xdg_directorydir) +dnl for KDE 2 +AC_SUBST(kde_templatesdir) +AC_SUBST(kde_servicesdir) +AC_SUBST(kde_servicetypesdir) +AC_SUBST(kde_moduledir) +AC_SUBST(kdeinitdir, '$(kde_moduledir)') +AC_SUBST(kde_styledir) +AC_SUBST(kde_widgetdir) +if test "$kde_qtver" = 1; then + kde_minidir="$kde_icondir/mini" +else +# for KDE 1 - this breaks KDE2 apps using minidir, but +# that's the plan ;-/ + kde_minidir="/dev/null" +fi +dnl AC_SUBST(kde_minidir) +dnl AC_SUBST(kde_cgidir) +dnl AC_SUBST(kde_toolbardir) +]) + +AC_DEFUN([KDE_MISC_TESTS], +[ + dnl Checks for libraries. + AC_CHECK_LIB(util, main, [LIBUTIL="-lutil"]) dnl for *BSD + AC_SUBST(LIBUTIL) + AC_CHECK_LIB(compat, main, [LIBCOMPAT="-lcompat"]) dnl for *BSD + AC_SUBST(LIBCOMPAT) + kde_have_crypt= + AC_CHECK_LIB(crypt, crypt, [LIBCRYPT="-lcrypt"; kde_have_crypt=yes], + AC_CHECK_LIB(c, crypt, [kde_have_crypt=yes], [ + AC_MSG_WARN([you have no crypt in either libcrypt or libc. +You should install libcrypt from another source or configure with PAM +support]) + kde_have_crypt=no + ])) + AC_SUBST(LIBCRYPT) + if test $kde_have_crypt = yes; then + AC_DEFINE_UNQUOTED(HAVE_CRYPT, 1, [Defines if your system has the crypt function]) + fi + AC_CHECK_SOCKLEN_T + AC_CHECK_LIB(dnet, dnet_ntoa, [X_EXTRA_LIBS="$X_EXTRA_LIBS -ldnet"]) + if test $ac_cv_lib_dnet_dnet_ntoa = no; then + AC_CHECK_LIB(dnet_stub, dnet_ntoa, + [X_EXTRA_LIBS="$X_EXTRA_LIBS -ldnet_stub"]) + fi + AC_CHECK_FUNC(inet_ntoa) + if test $ac_cv_func_inet_ntoa = no; then + AC_CHECK_LIB(nsl, inet_ntoa, X_EXTRA_LIBS="$X_EXTRA_LIBS -lnsl") + fi + AC_CHECK_FUNC(connect) + if test $ac_cv_func_connect = no; then + AC_CHECK_LIB(socket, connect, X_EXTRA_LIBS="-lsocket $X_EXTRA_LIBS", , + $X_EXTRA_LIBS) + fi + + AC_CHECK_FUNC(remove) + if test $ac_cv_func_remove = no; then + AC_CHECK_LIB(posix, remove, X_EXTRA_LIBS="$X_EXTRA_LIBS -lposix") + fi + + # BSDI BSD/OS 2.1 needs -lipc for XOpenDisplay. + AC_CHECK_FUNC(shmat, , + AC_CHECK_LIB(ipc, shmat, X_EXTRA_LIBS="$X_EXTRA_LIBS -lipc")) + + # more headers that need to be explicitly included on darwin + AC_CHECK_HEADERS(sys/types.h stdint.h) + + # sys/bitypes.h is needed for uint32_t and friends on Tru64 + AC_CHECK_HEADERS(sys/bitypes.h) + + # darwin requires a poll emulation library + AC_CHECK_LIB(poll, poll, LIB_POLL="-lpoll") + + # for some image handling on Mac OS X + AC_CHECK_HEADERS(Carbon/Carbon.h) + + # CoreAudio framework + AC_CHECK_HEADER(CoreAudio/CoreAudio.h, [ + AC_DEFINE(HAVE_COREAUDIO, 1, [Define if you have the CoreAudio API]) + FRAMEWORK_COREAUDIO="-Xlinker -framework -Xlinker CoreAudio" + ]) + + AC_CHECK_RES_INIT + AC_SUBST(LIB_POLL) + AC_SUBST(FRAMEWORK_COREAUDIO) + LIBSOCKET="$X_EXTRA_LIBS" + AC_SUBST(LIBSOCKET) + AC_SUBST(X_EXTRA_LIBS) + AC_CHECK_LIB(ucb, killpg, [LIBUCB="-lucb"]) dnl for Solaris2.4 + AC_SUBST(LIBUCB) + + case $host in dnl this *is* LynxOS specific + *-*-lynxos* ) + AC_MSG_CHECKING([LynxOS header file wrappers]) + [CFLAGS="$CFLAGS -D__NO_INCLUDE_WARN__"] + AC_MSG_RESULT(disabled) + AC_CHECK_LIB(bsd, gethostbyname, [LIBSOCKET="-lbsd"]) dnl for LynxOS + ;; + esac + + KDE_CHECK_TYPES + KDE_CHECK_LIBDL + KDE_CHECK_STRLCPY + +# darwin needs this to initialize the environment +AC_CHECK_HEADERS(crt_externs.h) +AC_CHECK_FUNC(_NSGetEnviron, [AC_DEFINE(HAVE_NSGETENVIRON, 1, [Define if your system needs _NSGetEnviron to set up the environment])]) + +AH_VERBATIM(_DARWIN_ENVIRON, +[ +#if defined(HAVE_NSGETENVIRON) && defined(HAVE_CRT_EXTERNS_H) +# include +# include +# define environ (*_NSGetEnviron()) +#endif +]) + +AH_VERBATIM(_AIX_STRINGS_H_BZERO, +[ +/* + * AIX defines FD_SET in terms of bzero, but fails to include + * that defines bzero. + */ + +#if defined(_AIX) +#include +#endif +]) + +AC_CHECK_FUNCS([vsnprintf snprintf]) + +AH_VERBATIM(_TRU64,[ +/* + * On HP-UX, the declaration of vsnprintf() is needed every time ! + */ + +#if !defined(HAVE_VSNPRINTF) || defined(hpux) +#if __STDC__ +#include +#include +#else +#include +#endif +#ifdef __cplusplus +extern "C" +#endif +int vsnprintf(char *str, size_t n, char const *fmt, va_list ap); +#ifdef __cplusplus +extern "C" +#endif +int snprintf(char *str, size_t n, char const *fmt, ...); +#endif +]) + +]) + +dnl ------------------------------------------------------------------------ +dnl Find the header files and libraries for X-Windows. Extended the +dnl macro AC_PATH_X +dnl ------------------------------------------------------------------------ +dnl +AC_DEFUN([K_PATH_X], +[ +AC_REQUIRE([KDE_MISC_TESTS])dnl +AC_REQUIRE([KDE_CHECK_LIB64]) + +AC_ARG_ENABLE( + embedded, + AC_HELP_STRING([--enable-embedded],[link to Qt-embedded, don't use X]), + kde_use_qt_emb=$enableval, + kde_use_qt_emb=no +) + +AC_ARG_ENABLE( + qtopia, + AC_HELP_STRING([--enable-qtopia],[link to Qt-embedded, link to the Qtopia Environment]), + kde_use_qt_emb_palm=$enableval, + kde_use_qt_emb_palm=no +) + +AC_ARG_ENABLE( + mac, + AC_HELP_STRING([--enable-mac],[link to Qt/Mac (don't use X)]), + kde_use_qt_mac=$enableval, + kde_use_qt_mac=no +) + +# used to disable x11-specific stuff on special platforms +AM_CONDITIONAL(include_x11, test "$kde_use_qt_emb" = "no" && test "$kde_use_qt_mac" = "no") + +if test "$kde_use_qt_emb" = "no" && test "$kde_use_qt_mac" = "no"; then + +AC_MSG_CHECKING(for X) + +AC_CACHE_VAL(kde_cv_have_x, +[# One or both of the vars are not set, and there is no cached value. +if test "{$x_includes+set}" = set || test "$x_includes" = NONE; then + kde_x_includes=NO +else + kde_x_includes=$x_includes +fi +if test "{$x_libraries+set}" = set || test "$x_libraries" = NONE; then + kde_x_libraries=NO +else + kde_x_libraries=$x_libraries +fi + +# below we use the standard autoconf calls +ac_x_libraries=$kde_x_libraries +ac_x_includes=$kde_x_includes + +KDE_PATH_X_DIRECT +dnl AC_PATH_X_XMKMF picks /usr/lib as the path for the X libraries. +dnl Unfortunately, if compiling with the N32 ABI, this is not the correct +dnl location. The correct location is /usr/lib32 or an undefined value +dnl (the linker is smart enough to pick the correct default library). +dnl Things work just fine if you use just AC_PATH_X_DIRECT. +dnl Solaris has a similar problem. AC_PATH_X_XMKMF forces x_includes to +dnl /usr/openwin/include, which doesn't work. /usr/include does work, so +dnl x_includes should be left alone. +case "$host" in +mips-sgi-irix6*) + ;; +*-*-solaris*) + ;; +*) + _AC_PATH_X_XMKMF + if test -z "$ac_x_includes"; then + ac_x_includes="." + fi + if test -z "$ac_x_libraries"; then + ac_x_libraries="/usr/lib${kdelibsuff}" + fi +esac +#from now on we use our own again + +# when the user already gave --x-includes, we ignore +# what the standard autoconf macros told us. +if test "$kde_x_includes" = NO; then + kde_x_includes=$ac_x_includes +fi + +# for --x-libraries too +if test "$kde_x_libraries" = NO; then + kde_x_libraries=$ac_x_libraries +fi + +if test "$kde_x_includes" = NO; then + AC_MSG_ERROR([Can't find X includes. Please check your installation and add the correct paths!]) +fi + +if test "$kde_x_libraries" = NO; then + AC_MSG_ERROR([Can't find X libraries. Please check your installation and add the correct paths!]) +fi + +# Record where we found X for the cache. +kde_cv_have_x="have_x=yes \ + kde_x_includes=$kde_x_includes kde_x_libraries=$kde_x_libraries" +])dnl + +eval "$kde_cv_have_x" + +if test "$have_x" != yes; then + AC_MSG_RESULT($have_x) + no_x=yes +else + AC_MSG_RESULT([libraries $kde_x_libraries, headers $kde_x_includes]) +fi + +if test -z "$kde_x_includes" || test "x$kde_x_includes" = xNONE; then + X_INCLUDES="" + x_includes="."; dnl better than nothing :- + else + x_includes=$kde_x_includes + X_INCLUDES="-I$x_includes" +fi + +if test -z "$kde_x_libraries" || test "x$kde_x_libraries" = xNONE; then + X_LDFLAGS="" + x_libraries="/usr/lib"; dnl better than nothing :- + else + x_libraries=$kde_x_libraries + X_LDFLAGS="-L$x_libraries" +fi +all_includes="$X_INCLUDES" +all_libraries="$X_LDFLAGS $LDFLAGS_AS_NEEDED $LDFLAGS_NEW_DTAGS" + +# Check for libraries that X11R6 Xt/Xaw programs need. +ac_save_LDFLAGS="$LDFLAGS" +LDFLAGS="$LDFLAGS $X_LDFLAGS" +# SM needs ICE to (dynamically) link under SunOS 4.x (so we have to +# check for ICE first), but we must link in the order -lSM -lICE or +# we get undefined symbols. So assume we have SM if we have ICE. +# These have to be linked with before -lX11, unlike the other +# libraries we check for below, so use a different variable. +# --interran@uluru.Stanford.EDU, kb@cs.umb.edu. +AC_CHECK_LIB(ICE, IceConnectionNumber, + [LIBSM="-lSM -lICE"], , $X_EXTRA_LIBS) +LDFLAGS="$ac_save_LDFLAGS" + +LIB_X11='-lX11 $(LIBSOCKET)' + +AC_MSG_CHECKING(for libXext) +AC_CACHE_VAL(kde_cv_have_libXext, +[ +kde_ldflags_safe="$LDFLAGS" +kde_libs_safe="$LIBS" + +LDFLAGS="$LDFLAGS $X_LDFLAGS $USER_LDFLAGS" +LIBS="-lXext -lX11 $LIBSOCKET" + +AC_TRY_LINK([ +#include +#ifdef STDC_HEADERS +# include +#endif +], +[ +printf("hello Xext\n"); +], +kde_cv_have_libXext=yes, +kde_cv_have_libXext=no +) + +LDFLAGS=$kde_ldflags_safe +LIBS=$kde_libs_safe +]) + +AC_MSG_RESULT($kde_cv_have_libXext) + +if test "$kde_cv_have_libXext" = "no"; then + AC_MSG_ERROR([We need a working libXext to proceed. Since configure +can't find it itself, we stop here assuming that make wouldn't find +them either.]) +fi + +LIB_XEXT="-lXext" +QTE_NORTTI="" + +elif test "$kde_use_qt_emb" = "yes"; then + dnl We're using QT Embedded + CPPFLAGS=-DQWS + CXXFLAGS="$CXXFLAGS -fno-rtti" + QTE_NORTTI="-fno-rtti -DQWS" + X_PRE_LIBS="" + LIB_X11="" + LIB_XEXT="" + LIB_XRENDER="" + LIBSM="" + X_INCLUDES="" + X_LDFLAGS="" + x_includes="" + x_libraries="" +elif test "$kde_use_qt_mac" = "yes"; then + dnl We're using QT/Mac (I use QT_MAC so that qglobal.h doesn't *have* to + dnl be included to get the information) --Sam + CXXFLAGS="$CXXFLAGS -DQT_MAC -no-cpp-precomp" + CFLAGS="$CFLAGS -DQT_MAC -no-cpp-precomp" + X_PRE_LIBS="" + LIB_X11="" + LIB_XEXT="" + LIB_XRENDER="" + LIBSM="" + X_INCLUDES="" + X_LDFLAGS="" + x_includes="" + x_libraries="" +fi +AC_SUBST(X_PRE_LIBS) +AC_SUBST(LIB_X11) +AC_SUBST(LIB_XRENDER) +AC_SUBST(LIBSM) +AC_SUBST(X_INCLUDES) +AC_SUBST(X_LDFLAGS) +AC_SUBST(x_includes) +AC_SUBST(x_libraries) +AC_SUBST(QTE_NORTTI) +AC_SUBST(LIB_XEXT) + +]) + +AC_DEFUN([KDE_PRINT_QT_PROGRAM], +[ +AC_REQUIRE([KDE_USE_QT]) +cat > conftest.$ac_ext < +#include +EOF +if test "$kde_qtver" = "2"; then +cat >> conftest.$ac_ext < +#include +#include +EOF + +if test $kde_qtsubver -gt 0; then +cat >> conftest.$ac_ext <> conftest.$ac_ext < +#include +#include +EOF +fi + +echo "#if ! ($kde_qt_verstring)" >> conftest.$ac_ext +cat >> conftest.$ac_ext <> conftest.$ac_ext <> conftest.$ac_ext <> conftest.$ac_ext <> conftest.$ac_ext <&AC_FD_CC + cat conftest.$ac_ext >&AC_FD_CC +fi + +rm -f conftest* +CXXFLAGS="$ac_cxxflags_safe" +LDFLAGS="$ac_ldflags_safe" +LIBS="$ac_libs_safe" + +LD_LIBRARY_PATH="$ac_LD_LIBRARY_PATH_safe" +export LD_LIBRARY_PATH +LIBRARY_PATH="$ac_LIBRARY_PATH" +export LIBRARY_PATH +AC_LANG_RESTORE +]) + +if test "$kde_cv_qt_direct" = "yes"; then + AC_MSG_RESULT(yes) + $1 +else + AC_MSG_RESULT(no) + $2 +fi +]) + +dnl ------------------------------------------------------------------------ +dnl Try to find the Qt headers and libraries. +dnl $(QT_LDFLAGS) will be -Lqtliblocation (if needed) +dnl and $(QT_INCLUDES) will be -Iqthdrlocation (if needed) +dnl ------------------------------------------------------------------------ +dnl +AC_DEFUN([AC_PATH_QT_1_3], +[ +AC_REQUIRE([K_PATH_X]) +AC_REQUIRE([KDE_USE_QT]) +AC_REQUIRE([KDE_CHECK_LIB64]) + +dnl ------------------------------------------------------------------------ +dnl Add configure flag to enable linking to MT version of Qt library. +dnl ------------------------------------------------------------------------ + +AC_ARG_ENABLE( + mt, + AC_HELP_STRING([--disable-mt],[link to non-threaded Qt (deprecated)]), + kde_use_qt_mt=$enableval, + [ + if test $kde_qtver = 3; then + kde_use_qt_mt=yes + else + kde_use_qt_mt=no + fi + ] +) + +USING_QT_MT="" + +dnl ------------------------------------------------------------------------ +dnl If we not get --disable-qt-mt then adjust some vars for the host. +dnl ------------------------------------------------------------------------ + +KDE_MT_LDFLAGS= +KDE_MT_LIBS= +if test "x$kde_use_qt_mt" = "xyes"; then + KDE_CHECK_THREADING + if test "x$kde_use_threading" = "xyes"; then + CPPFLAGS="$USE_THREADS -DQT_THREAD_SUPPORT $CPPFLAGS" + KDE_MT_LDFLAGS="$USE_THREADS" + KDE_MT_LIBS="$LIBPTHREAD" + else + kde_use_qt_mt=no + fi +fi +AC_SUBST(KDE_MT_LDFLAGS) +AC_SUBST(KDE_MT_LIBS) + +kde_qt_was_given=yes + +dnl ------------------------------------------------------------------------ +dnl If we haven't been told how to link to Qt, we work it out for ourselves. +dnl ------------------------------------------------------------------------ +if test -z "$LIBQT_GLOB"; then + if test "x$kde_use_qt_emb" = "xyes"; then + LIBQT_GLOB="libqte.*" + else + LIBQT_GLOB="libqt.*" + fi +fi + +if test -z "$LIBQT"; then +dnl ------------------------------------------------------------ +dnl If we got --enable-embedded then adjust the Qt library name. +dnl ------------------------------------------------------------ + if test "x$kde_use_qt_emb" = "xyes"; then + qtlib="qte" + else + qtlib="qt" + fi + + kde_int_qt="-l$qtlib" +else + kde_int_qt="$LIBQT" + kde_lib_qt_set=yes +fi + +if test -z "$LIBQPE"; then +dnl ------------------------------------------------------------ +dnl If we got --enable-palmtop then add -lqpe to the link line +dnl ------------------------------------------------------------ + if test "x$kde_use_qt_emb" = "xyes"; then + if test "x$kde_use_qt_emb_palm" = "xyes"; then + LIB_QPE="-lqpe" + else + LIB_QPE="" + fi + else + LIB_QPE="" + fi +fi + +dnl ------------------------------------------------------------------------ +dnl If we got --enable-qt-mt then adjust the Qt library name for the host. +dnl ------------------------------------------------------------------------ + +if test "x$kde_use_qt_mt" = "xyes"; then + if test -z "$LIBQT"; then + LIBQT="-l$qtlib-mt" + kde_int_qt="-l$qtlib-mt" + else + LIBQT="$qtlib-mt" + kde_int_qt="$qtlib-mt" + fi + LIBQT_GLOB="lib$qtlib-mt.*" + USING_QT_MT="using -mt" +else + LIBQT="-l$qtlib" +fi + +if test $kde_qtver != 1; then + + AC_REQUIRE([AC_FIND_PNG]) + AC_REQUIRE([AC_FIND_JPEG]) + LIBQT="$LIBQT $LIBPNG $LIBJPEG" +fi + +if test $kde_qtver = 3; then + AC_REQUIRE([KDE_CHECK_LIBDL]) + LIBQT="$LIBQT $LIBDL" +fi + +AC_MSG_CHECKING([for Qt]) + +if test "x$kde_use_qt_emb" != "xyes" && test "x$kde_use_qt_mac" != "xyes"; then +LIBQT="$LIBQT $X_PRE_LIBS -lXext -lX11 $LIBSM $LIBSOCKET" +fi +ac_qt_includes=NO ac_qt_libraries=NO ac_qt_bindir=NO +qt_libraries="" +qt_includes="" +AC_ARG_WITH(qt-dir, + AC_HELP_STRING([--with-qt-dir=DIR],[where the root of Qt is installed ]), + [ ac_qt_includes="$withval"/include + ac_qt_libraries="$withval"/lib${kdelibsuff} + ac_qt_bindir="$withval"/bin + ]) + +AC_ARG_WITH(qt-includes, + AC_HELP_STRING([--with-qt-includes=DIR],[where the Qt includes are. ]), + [ + ac_qt_includes="$withval" + ]) + +kde_qt_libs_given=no + +AC_ARG_WITH(qt-libraries, + AC_HELP_STRING([--with-qt-libraries=DIR],[where the Qt library is installed.]), + [ ac_qt_libraries="$withval" + kde_qt_libs_given=yes + ]) + +AC_CACHE_VAL(ac_cv_have_qt, +[#try to guess Qt locations + +qt_incdirs="" +for dir in $kde_qt_dirs; do + qt_incdirs="$qt_incdirs $dir/include $dir" +done +qt_incdirs="$QTINC $qt_incdirs /usr/local/qt/include /usr/include/qt /usr/include /usr/X11R6/include/X11/qt /usr/X11R6/include/qt /usr/X11R6/include/qt2 /usr/include/qt3 $x_includes" +if test ! "$ac_qt_includes" = "NO"; then + qt_incdirs="$ac_qt_includes $qt_incdirs" +fi + +if test "$kde_qtver" != "1"; then + kde_qt_header=qstyle.h +else + kde_qt_header=qglobal.h +fi + +AC_FIND_FILE($kde_qt_header, $qt_incdirs, qt_incdir) +ac_qt_includes="$qt_incdir" + +qt_libdirs="" +for dir in $kde_qt_dirs; do + qt_libdirs="$qt_libdirs $dir/lib${kdelibsuff} $dir" +done +qt_libdirs="$QTLIB $qt_libdirs /usr/X11R6/lib /usr/lib /usr/local/qt/lib $x_libraries" +if test ! "$ac_qt_libraries" = "NO"; then + qt_libdir=$ac_qt_libraries +else + qt_libdirs="$ac_qt_libraries $qt_libdirs" + # if the Qt was given, the chance is too big that libqt.* doesn't exist + qt_libdir=NONE + for dir in $qt_libdirs; do + try="ls -1 $dir/${LIBQT_GLOB}" + if test -n "`$try 2> /dev/null`"; then qt_libdir=$dir; break; else echo "tried $dir" >&AC_FD_CC ; fi + done +fi +for a in $qt_libdir/lib`echo ${kde_int_qt} | sed 's,^-l,,'`_incremental.*; do + if test -e "$a"; then + LIBQT="$LIBQT ${kde_int_qt}_incremental" + break + fi +done + +ac_qt_libraries="$qt_libdir" + +AC_LANG_SAVE +AC_LANG_CPLUSPLUS + +ac_cxxflags_safe="$CXXFLAGS" +ac_ldflags_safe="$LDFLAGS" +ac_libs_safe="$LIBS" + +CXXFLAGS="$CXXFLAGS -I$qt_incdir $all_includes" +LDFLAGS="$LDFLAGS -L$qt_libdir $all_libraries $USER_LDFLAGS $KDE_MT_LDFLAGS" +LIBS="$LIBS $LIBQT $KDE_MT_LIBS" + +KDE_PRINT_QT_PROGRAM + +if AC_TRY_EVAL(ac_link) && test -s conftest; then + rm -f conftest* +else + echo "configure: failed program was:" >&AC_FD_CC + cat conftest.$ac_ext >&AC_FD_CC + ac_qt_libraries="NO" +fi +rm -f conftest* +CXXFLAGS="$ac_cxxflags_safe" +LDFLAGS="$ac_ldflags_safe" +LIBS="$ac_libs_safe" + +AC_LANG_RESTORE +if test "$ac_qt_includes" = NO || test "$ac_qt_libraries" = NO; then + ac_cv_have_qt="have_qt=no" + ac_qt_notfound="" + missing_qt_mt="" + if test "$ac_qt_includes" = NO; then + if test "$ac_qt_libraries" = NO; then + ac_qt_notfound="(headers and libraries)"; + else + ac_qt_notfound="(headers)"; + fi + else + if test "x$kde_use_qt_mt" = "xyes"; then + missing_qt_mt=" +Make sure that you have compiled Qt with thread support!" + ac_qt_notfound="(library $qtlib-mt)"; + else + ac_qt_notfound="(library $qtlib)"; + fi + fi + + AC_MSG_ERROR([Qt ($kde_qt_minversion) $ac_qt_notfound not found. Please check your installation! +For more details about this problem, look at the end of config.log.$missing_qt_mt]) +else + have_qt="yes" +fi +]) + +eval "$ac_cv_have_qt" + +if test "$have_qt" != yes; then + AC_MSG_RESULT([$have_qt]); +else + ac_cv_have_qt="have_qt=yes \ + ac_qt_includes=$ac_qt_includes ac_qt_libraries=$ac_qt_libraries" + AC_MSG_RESULT([libraries $ac_qt_libraries, headers $ac_qt_includes $USING_QT_MT]) + + qt_libraries="$ac_qt_libraries" + qt_includes="$ac_qt_includes" +fi + +if test ! "$kde_qt_libs_given" = "yes" && test ! "$kde_qtver" = 3; then + KDE_CHECK_QT_DIRECT(qt_libraries= ,[]) +fi + +AC_SUBST(qt_libraries) +AC_SUBST(qt_includes) + +if test "$qt_includes" = "$x_includes" || test -z "$qt_includes"; then + QT_INCLUDES="" +else + QT_INCLUDES="-I$qt_includes" + all_includes="$QT_INCLUDES $all_includes" +fi + +if test "$qt_libraries" = "$x_libraries" || test -z "$qt_libraries"; then + QT_LDFLAGS="" +else + QT_LDFLAGS="-L$qt_libraries" + all_libraries="$QT_LDFLAGS $all_libraries" +fi +test -z "$KDE_MT_LDFLAGS" || all_libraries="$all_libraries $KDE_MT_LDFLAGS" + +AC_SUBST(QT_INCLUDES) +AC_SUBST(QT_LDFLAGS) +AC_PATH_QT_MOC_UIC + +KDE_CHECK_QT_JPEG + +if test "x$kde_use_qt_emb" != "xyes" && test "x$kde_use_qt_mac" != "xyes"; then +LIB_QT="$kde_int_qt $LIBJPEG_QT "'$(LIBZ) $(LIBPNG) -lXext $(LIB_X11) $(LIBSM)' +else +LIB_QT="$kde_int_qt $LIBJPEG_QT "'$(LIBZ) $(LIBPNG)' +fi +test -z "$KDE_MT_LIBS" || LIB_QT="$LIB_QT $KDE_MT_LIBS" +for a in $qt_libdir/lib`echo ${kde_int_qt} | sed 's,^-l,,'`_incremental.*; do + if test -e "$a"; then + LIB_QT="$LIB_QT ${kde_int_qt}_incremental" + break + fi +done + +AC_SUBST(LIB_QT) +AC_SUBST(LIB_QPE) + +AC_SUBST(kde_qtver) +]) + +AC_DEFUN([AC_PATH_QT], +[ +AC_PATH_QT_1_3 +]) + +AC_DEFUN([KDE_CHECK_UIC_PLUGINS], +[ +AC_REQUIRE([AC_PATH_QT_MOC_UIC]) + +if test x$ac_uic_supports_libpath = xyes; then + +AC_MSG_CHECKING([if UIC has KDE plugins available]) +AC_CACHE_VAL(kde_cv_uic_plugins, +[ +cat > actest.ui << EOF + +NewConnectionDialog + + + + testInput + + + + +EOF + + + +kde_cv_uic_plugins=no +kde_line="$UIC_PATH -L $kde_widgetdir" +if test x$ac_uic_supports_nounload = xyes; then + kde_line="$kde_line -nounload" +fi +kde_line="$kde_line -impl actest.h actest.ui > actest.cpp" +if AC_TRY_EVAL(kde_line); then + # if you're trying to debug this check and think it's incorrect, + # better check your installation. The check _is_ correct - your + # installation is not. + if test -f actest.cpp && grep klineedit actest.cpp > /dev/null; then + kde_cv_uic_plugins=yes + fi +fi +rm -f actest.ui actest.cpp +]) + +AC_MSG_RESULT([$kde_cv_uic_plugins]) +if test "$kde_cv_uic_plugins" != yes; then + AC_MSG_ERROR([you need to install kdelibs first.]) +fi +fi +]) + +AC_DEFUN([KDE_CHECK_FINAL], +[ + AC_ARG_ENABLE(final, + AC_HELP_STRING([--enable-final], + [build size optimized apps (experimental - needs lots of memory)]), + kde_use_final=$enableval, kde_use_final=no) + + if test "x$kde_use_final" = "xyes"; then + KDE_USE_FINAL_TRUE="" + KDE_USE_FINAL_FALSE="#" + else + KDE_USE_FINAL_TRUE="#" + KDE_USE_FINAL_FALSE="" + fi + AC_SUBST(KDE_USE_FINAL_TRUE) + AC_SUBST(KDE_USE_FINAL_FALSE) +]) + +AC_DEFUN([KDE_CHECK_CLOSURE], +[ + AC_ARG_ENABLE(closure, + AC_HELP_STRING([--enable-closure],[delay template instantiation]), + kde_use_closure=$enableval, kde_use_closure=no) + + KDE_NO_UNDEFINED="" + if test "x$kde_use_closure" = "xyes"; then + KDE_USE_CLOSURE_TRUE="" + KDE_USE_CLOSURE_FALSE="#" +# CXXFLAGS="$CXXFLAGS $REPO" + else + KDE_USE_CLOSURE_TRUE="#" + KDE_USE_CLOSURE_FALSE="" + KDE_NO_UNDEFINED="" + case $host in + *-*-linux-gnu) + KDE_CHECK_COMPILER_FLAG([Wl,--no-undefined], + [KDE_CHECK_COMPILER_FLAG([Wl,--allow-shlib-undefined], + [KDE_NO_UNDEFINED="-Wl,--no-undefined -Wl,--allow-shlib-undefined"], + [KDE_NO_UNDEFINED=""])], + [KDE_NO_UNDEFINED=""]) + ;; + esac + fi + AC_SUBST(KDE_USE_CLOSURE_TRUE) + AC_SUBST(KDE_USE_CLOSURE_FALSE) + AC_SUBST(KDE_NO_UNDEFINED) +]) + +dnl Check if the linker supports --enable-new-dtags and --as-needed +AC_DEFUN([KDE_CHECK_NEW_LDFLAGS], +[ + AC_ARG_ENABLE(new_ldflags, + AC_HELP_STRING([--enable-new-ldflags], + [enable the new linker flags]), + kde_use_new_ldflags=$enableval, + kde_use_new_ldflags=no) + + LDFLAGS_AS_NEEDED="" + LDFLAGS_NEW_DTAGS="" + if test "x$kde_use_new_ldflags" = "xyes"; then + LDFLAGS_NEW_DTAGS="" + KDE_CHECK_COMPILER_FLAG([Wl,--enable-new-dtags], + [LDFLAGS_NEW_DTAGS="-Wl,--enable-new-dtags"],) + + KDE_CHECK_COMPILER_FLAG([Wl,--as-needed], + [LDFLAGS_AS_NEEDED="-Wl,--as-needed"],) + fi + AC_SUBST(LDFLAGS_AS_NEEDED) + AC_SUBST(LDFLAGS_NEW_DTAGS) +]) + +AC_DEFUN([KDE_CHECK_NMCHECK], +[ + AC_ARG_ENABLE(nmcheck,AC_HELP_STRING([--enable-nmcheck],[enable automatic namespace cleanness check]), + kde_use_nmcheck=$enableval, kde_use_nmcheck=no) + + if test "$kde_use_nmcheck" = "yes"; then + KDE_USE_NMCHECK_TRUE="" + KDE_USE_NMCHECK_FALSE="#" + else + KDE_USE_NMCHECK_TRUE="#" + KDE_USE_NMCHECK_FALSE="" + fi + AC_SUBST(KDE_USE_NMCHECK_TRUE) + AC_SUBST(KDE_USE_NMCHECK_FALSE) +]) + +AC_DEFUN([KDE_EXPAND_MAKEVAR], [ +savex=$exec_prefix +test "x$exec_prefix" = xNONE && exec_prefix=$prefix +tmp=$$2 +while $1=`eval echo "$tmp"`; test "x$$1" != "x$tmp"; do tmp=$$1; done +exec_prefix=$savex +]) + +dnl ------------------------------------------------------------------------ +dnl Now, the same with KDE +dnl $(KDE_LDFLAGS) will be the kdeliblocation (if needed) +dnl and $(kde_includes) will be the kdehdrlocation (if needed) +dnl ------------------------------------------------------------------------ +dnl +AC_DEFUN([AC_BASE_PATH_KDE], +[ +AC_REQUIRE([KDE_CHECK_STL]) +AC_REQUIRE([AC_PATH_QT])dnl +AC_REQUIRE([KDE_CHECK_LIB64]) + +AC_CHECK_RPATH +AC_MSG_CHECKING([for KDE]) + +if test "${prefix}" != NONE; then + kde_includes=${includedir} + KDE_EXPAND_MAKEVAR(ac_kde_includes, includedir) + + kde_libraries=${libdir} + KDE_EXPAND_MAKEVAR(ac_kde_libraries, libdir) + +else + ac_kde_includes= + ac_kde_libraries= + kde_libraries="" + kde_includes="" +fi + +AC_CACHE_VAL(ac_cv_have_kde, +[#try to guess kde locations + +if test "$kde_qtver" = 1; then + kde_check_header="ksock.h" + kde_check_lib="libkdecore.la" +else + kde_check_header="ksharedptr.h" + kde_check_lib="libkio.la" +fi + +if test -z "$1"; then + +kde_incdirs="$kde_libs_prefix/include /usr/lib/kde/include /usr/local/kde/include /usr/local/include /usr/kde/include /usr/include/kde /usr/include /opt/kde3/include /opt/kde/include $x_includes $qt_includes" +test -n "$KDEDIR" && kde_incdirs="$KDEDIR/include $KDEDIR/include/kde $KDEDIR $kde_incdirs" +kde_incdirs="$ac_kde_includes $kde_incdirs" +AC_FIND_FILE($kde_check_header, $kde_incdirs, kde_incdir) +ac_kde_includes="$kde_incdir" + +if test -n "$ac_kde_includes" && test ! -r "$ac_kde_includes/$kde_check_header"; then + AC_MSG_ERROR([ +in the prefix, you've chosen, are no KDE headers installed. This will fail. +So, check this please and use another prefix!]) +fi + +kde_libdirs="$kde_libs_prefix/lib${kdelibsuff} /usr/lib/kde/lib${kdelibsuff} /usr/local/kde/lib${kdelibsuff} /usr/kde/lib${kdelibsuff} /usr/lib${kdelibsuff}/kde /usr/lib${kdelibsuff}/kde3 /usr/lib${kdelibsuff} /usr/X11R6/lib${kdelibsuff} /usr/local/lib${kdelibsuff} /opt/kde3/lib${kdelibsuff} /opt/kde/lib${kdelibsuff} /usr/X11R6/kde/lib${kdelibsuff}" +test -n "$KDEDIR" && kde_libdirs="$KDEDIR/lib${kdelibsuff} $KDEDIR $kde_libdirs" +kde_libdirs="$ac_kde_libraries $libdir $kde_libdirs" +AC_FIND_FILE($kde_check_lib, $kde_libdirs, kde_libdir) +ac_kde_libraries="$kde_libdir" + +kde_widgetdir=NO +dnl this might be somewhere else +AC_FIND_FILE("kde3/plugins/designer/kdewidgets.la", $kde_libdirs, kde_widgetdir) + +if test -n "$ac_kde_libraries" && test ! -r "$ac_kde_libraries/$kde_check_lib"; then +AC_MSG_ERROR([ +in the prefix, you've chosen, are no KDE libraries installed. This will fail. +So, check this please and use another prefix!]) +fi + +if test -n "$kde_widgetdir" && test ! -r "$kde_widgetdir/kde3/plugins/designer/kdewidgets.la"; then +AC_MSG_ERROR([ +I can't find the designer plugins. These are required and should have been installed +by kdelibs]) +fi + +if test -n "$kde_widgetdir"; then + kde_widgetdir="$kde_widgetdir/kde3/plugins/designer" +fi + + +if test "$ac_kde_includes" = NO || test "$ac_kde_libraries" = NO || test "$kde_widgetdir" = NO; then + ac_cv_have_kde="have_kde=no" +else + ac_cv_have_kde="have_kde=yes \ + ac_kde_includes=$ac_kde_includes ac_kde_libraries=$ac_kde_libraries" +fi + +else dnl test -z $1, e.g. from kdelibs + + ac_cv_have_kde="have_kde=no" + +fi +])dnl + +eval "$ac_cv_have_kde" + +if test "$have_kde" != "yes"; then + if test "${prefix}" = NONE; then + ac_kde_prefix="$ac_default_prefix" + else + ac_kde_prefix="$prefix" + fi + if test "$exec_prefix" = NONE; then + ac_kde_exec_prefix="$ac_kde_prefix" + AC_MSG_RESULT([will be installed in $ac_kde_prefix]) + else + ac_kde_exec_prefix="$exec_prefix" + AC_MSG_RESULT([will be installed in $ac_kde_prefix and $ac_kde_exec_prefix]) + fi + + kde_libraries="${libdir}" + kde_includes="${includedir}" + +else + ac_cv_have_kde="have_kde=yes \ + ac_kde_includes=$ac_kde_includes ac_kde_libraries=$ac_kde_libraries" + AC_MSG_RESULT([libraries $ac_kde_libraries, headers $ac_kde_includes]) + + kde_libraries="$ac_kde_libraries" + kde_includes="$ac_kde_includes" +fi +AC_SUBST(kde_libraries) +AC_SUBST(kde_includes) + +if test "$kde_includes" = "$x_includes" || test "$kde_includes" = "$qt_includes" || test "$kde_includes" = "/usr/include"; then + KDE_INCLUDES="" +else + KDE_INCLUDES="-I$kde_includes" + all_includes="$KDE_INCLUDES $all_includes" +fi + +KDE_DEFAULT_CXXFLAGS="-DQT_CLEAN_NAMESPACE -DQT_NO_ASCII_CAST -DQT_NO_STL -DQT_NO_COMPAT -DQT_NO_TRANSLATION" + +KDE_LDFLAGS="-L$kde_libraries" +if test ! "$kde_libraries" = "$x_libraries" && test ! "$kde_libraries" = "$qt_libraries" ; then + all_libraries="$KDE_LDFLAGS $all_libraries" +fi + +AC_SUBST(KDE_LDFLAGS) +AC_SUBST(KDE_INCLUDES) + +AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) + +all_libraries="$all_libraries $USER_LDFLAGS" +all_includes="$all_includes $USER_INCLUDES" +AC_SUBST(all_includes) +AC_SUBST(all_libraries) + +if test -z "$1"; then +KDE_CHECK_UIC_PLUGINS +fi + +ac_kde_libraries="$kde_libdir" + +AC_SUBST(AUTODIRS) + + +]) + +AC_DEFUN([KDE_CHECK_EXTRA_LIBS], +[ +AC_MSG_CHECKING(for extra includes) +AC_ARG_WITH(extra-includes,AC_HELP_STRING([--with-extra-includes=DIR],[adds non standard include paths]), + kde_use_extra_includes="$withval", + kde_use_extra_includes=NONE +) +kde_extra_includes= +if test -n "$kde_use_extra_includes" && \ + test "$kde_use_extra_includes" != "NONE"; then + + ac_save_ifs=$IFS + IFS=':' + for dir in $kde_use_extra_includes; do + kde_extra_includes="$kde_extra_includes $dir" + USER_INCLUDES="$USER_INCLUDES -I$dir" + done + IFS=$ac_save_ifs + kde_use_extra_includes="added" +else + kde_use_extra_includes="no" +fi +AC_SUBST(USER_INCLUDES) + +AC_MSG_RESULT($kde_use_extra_includes) + +kde_extra_libs= +AC_MSG_CHECKING(for extra libs) +AC_ARG_WITH(extra-libs,AC_HELP_STRING([--with-extra-libs=DIR],[adds non standard library paths]), + kde_use_extra_libs=$withval, + kde_use_extra_libs=NONE +) +if test -n "$kde_use_extra_libs" && \ + test "$kde_use_extra_libs" != "NONE"; then + + ac_save_ifs=$IFS + IFS=':' + for dir in $kde_use_extra_libs; do + kde_extra_libs="$kde_extra_libs $dir" + KDE_EXTRA_RPATH="$KDE_EXTRA_RPATH -R $dir" + USER_LDFLAGS="$USER_LDFLAGS -L$dir" + done + IFS=$ac_save_ifs + kde_use_extra_libs="added" +else + kde_use_extra_libs="no" +fi + +AC_SUBST(USER_LDFLAGS) + +AC_MSG_RESULT($kde_use_extra_libs) + +]) + +AC_DEFUN([KDE_1_CHECK_PATH_HEADERS], +[ + AC_MSG_CHECKING([for KDE headers installed]) + AC_LANG_SAVE + AC_LANG_CPLUSPLUS +cat > conftest.$ac_ext < +#endif +#include +#include "confdefs.h" +#include + +int main() { + printf("kde_htmldir=\\"%s\\"\n", KApplication::kde_htmldir().data()); + printf("kde_appsdir=\\"%s\\"\n", KApplication::kde_appsdir().data()); + printf("kde_icondir=\\"%s\\"\n", KApplication::kde_icondir().data()); + printf("kde_sounddir=\\"%s\\"\n", KApplication::kde_sounddir().data()); + printf("kde_datadir=\\"%s\\"\n", KApplication::kde_datadir().data()); + printf("kde_locale=\\"%s\\"\n", KApplication::kde_localedir().data()); + printf("kde_cgidir=\\"%s\\"\n", KApplication::kde_cgidir().data()); + printf("kde_confdir=\\"%s\\"\n", KApplication::kde_configdir().data()); + printf("kde_mimedir=\\"%s\\"\n", KApplication::kde_mimedir().data()); + printf("kde_toolbardir=\\"%s\\"\n", KApplication::kde_toolbardir().data()); + printf("kde_wallpaperdir=\\"%s\\"\n", + KApplication::kde_wallpaperdir().data()); + printf("kde_bindir=\\"%s\\"\n", KApplication::kde_bindir().data()); + printf("kde_partsdir=\\"%s\\"\n", KApplication::kde_partsdir().data()); + printf("kde_servicesdir=\\"/tmp/dummy\\"\n"); + printf("kde_servicetypesdir=\\"/tmp/dummy\\"\n"); + printf("kde_moduledir=\\"/tmp/dummy\\"\n"); + printf("kde_styledir=\\"/tmp/dummy\\"\n"); + printf("kde_widgetdir=\\"/tmp/dummy\\"\n"); + printf("xdg_appsdir=\\"/tmp/dummy\\"\n"); + printf("xdg_menudir=\\"/tmp/dummy\\"\n"); + printf("xdg_directorydir=\\"/tmp/dummy\\"\n"); + printf("kde_kcfgdir=\\"/tmp/dummy\\"\n"); + return 0; + } +EOF + + ac_save_CPPFLAGS=$CPPFLAGS + CPPFLAGS="$all_includes $CPPFLAGS" + if AC_TRY_EVAL(ac_compile); then + AC_MSG_RESULT(yes) + else + AC_MSG_ERROR([your system is not able to compile a small KDE application! +Check, if you installed the KDE header files correctly. +For more details about this problem, look at the end of config.log.]) + fi + CPPFLAGS=$ac_save_CPPFLAGS + + AC_LANG_RESTORE +]) + +AC_DEFUN([KDE_CHECK_KDEQTADDON], +[ +AC_MSG_CHECKING(for kde-qt-addon) +AC_CACHE_VAL(kde_cv_have_kdeqtaddon, +[ + kde_ldflags_safe="$LDFLAGS" + kde_libs_safe="$LIBS" + kde_cxxflags_safe="$CXXFLAGS" + + LIBS="-lkde-qt-addon $LIBQT $LIBS" + CXXFLAGS="$CXXFLAGS -I$prefix/include -I$prefix/include/kde $all_includes" + LDFLAGS="$LDFLAGS $all_libraries $USER_LDFLAGS" + + AC_TRY_LINK([ + #include + ], + [ + QDomDocument doc; + ], + kde_cv_have_kdeqtaddon=yes, + kde_cv_have_kdeqtaddon=no + ) + + LDFLAGS=$kde_ldflags_safe + LIBS=$kde_libs_safe + CXXFLAGS=$kde_cxxflags_safe +]) + +AC_MSG_RESULT($kde_cv_have_kdeqtaddon) + +if test "$kde_cv_have_kdeqtaddon" = "no"; then + AC_MSG_ERROR([Can't find libkde-qt-addon. You need to install it first. +It is a separate package (and CVS module) named kde-qt-addon.]) +fi +]) + +AC_DEFUN([KDE_CREATE_LIBS_ALIASES], +[ + AC_REQUIRE([KDE_MISC_TESTS]) + AC_REQUIRE([KDE_CHECK_LIBDL]) + AC_REQUIRE([K_PATH_X]) + +if test $kde_qtver = 3; then + AC_SUBST(LIB_KDECORE, "-lkdecore") + AC_SUBST(LIB_KDEUI, "-lkdeui") + AC_SUBST(LIB_KIO, "-lkio") + AC_SUBST(LIB_KJS, "-lkjs") + AC_SUBST(LIB_SMB, "-lsmb") + AC_SUBST(LIB_KAB, "-lkab") + AC_SUBST(LIB_KABC, "-lkabc") + AC_SUBST(LIB_KHTML, "-lkhtml") + AC_SUBST(LIB_KSPELL, "-lkspell") + AC_SUBST(LIB_KPARTS, "-lkparts") + AC_SUBST(LIB_KDEPRINT, "-lkdeprint") + AC_SUBST(LIB_KUTILS, "-lkutils") + AC_SUBST(LIB_KDEPIM, "-lkdepim") + AC_SUBST(LIB_KIMPROXY, "-lkimproxy") + AC_SUBST(LIB_KNEWSTUFF, "-lknewstuff") + AC_SUBST(LIB_KDNSSD, "-lkdnssd") +# these are for backward compatibility + AC_SUBST(LIB_KSYCOCA, "-lkio") + AC_SUBST(LIB_KFILE, "-lkio") +elif test $kde_qtver = 2; then + AC_SUBST(LIB_KDECORE, "-lkdecore") + AC_SUBST(LIB_KDEUI, "-lkdeui") + AC_SUBST(LIB_KIO, "-lkio") + AC_SUBST(LIB_KSYCOCA, "-lksycoca") + AC_SUBST(LIB_SMB, "-lsmb") + AC_SUBST(LIB_KFILE, "-lkfile") + AC_SUBST(LIB_KAB, "-lkab") + AC_SUBST(LIB_KHTML, "-lkhtml") + AC_SUBST(LIB_KSPELL, "-lkspell") + AC_SUBST(LIB_KPARTS, "-lkparts") + AC_SUBST(LIB_KDEPRINT, "-lkdeprint") +else + AC_SUBST(LIB_KDECORE, "-lkdecore -lXext $(LIB_QT)") + AC_SUBST(LIB_KDEUI, "-lkdeui $(LIB_KDECORE)") + AC_SUBST(LIB_KFM, "-lkfm $(LIB_KDECORE)") + AC_SUBST(LIB_KFILE, "-lkfile $(LIB_KFM) $(LIB_KDEUI)") + AC_SUBST(LIB_KAB, "-lkab $(LIB_KIMGIO) $(LIB_KDECORE)") +fi +]) + +AC_DEFUN([AC_PATH_KDE], +[ + AC_BASE_PATH_KDE + AC_ARG_ENABLE(path-check,AC_HELP_STRING([--disable-path-check],[don't try to find out, where to install]), + [ + if test "$enableval" = "no"; + then ac_use_path_checking="default" + else ac_use_path_checking="" + fi + ], + [ + if test "$kde_qtver" = 1; + then ac_use_path_checking="" + else ac_use_path_checking="default" + fi + ] + ) + + AC_CREATE_KFSSTND($ac_use_path_checking) + + AC_SUBST_KFSSTND + KDE_CREATE_LIBS_ALIASES +]) + +dnl KDE_CHECK_FUNC_EXT(, [headers], [sample-use], [C prototype], [autoheader define], [call if found]) +AC_DEFUN([KDE_CHECK_FUNC_EXT], +[ +AC_MSG_CHECKING(for $1) +AC_CACHE_VAL(kde_cv_func_$1, +[ +AC_LANG_SAVE +AC_LANG_CPLUSPLUS +save_CXXFLAGS="$CXXFLAGS" +kde_safe_LIBS="$LIBS" +LIBS="$LIBS $X_EXTRA_LIBS" +if test "$GXX" = "yes"; then +CXXFLAGS="$CXXFLAGS -pedantic-errors" +fi +AC_TRY_COMPILE([ +$2 +], +[ +$3 +], +kde_cv_func_$1=yes, +kde_cv_func_$1=no) +CXXFLAGS="$save_CXXFLAGS" +LIBS="$kde_safe_LIBS" +AC_LANG_RESTORE +]) + +AC_MSG_RESULT($kde_cv_func_$1) + +AC_MSG_CHECKING([if $1 needs custom prototype]) +AC_CACHE_VAL(kde_cv_proto_$1, +[ +if test "x$kde_cv_func_$1" = xyes; then + kde_cv_proto_$1=no +else + case "$1" in + setenv|unsetenv|usleep|random|srandom|seteuid|mkstemps|mkstemp|revoke|vsnprintf|strlcpy|strlcat) + kde_cv_proto_$1="yes - in libkdefakes" + ;; + *) + kde_cv_proto_$1=unknown + ;; + esac +fi + +if test "x$kde_cv_proto_$1" = xunknown; then + +AC_LANG_SAVE +AC_LANG_CPLUSPLUS + kde_safe_libs=$LIBS + LIBS="$LIBS $X_EXTRA_LIBS" + AC_TRY_LINK([ +$2 + +extern "C" $4; +], +[ +$3 +], +[ kde_cv_func_$1=yes + kde_cv_proto_$1=yes ], + [kde_cv_proto_$1="$1 unavailable"] +) +LIBS=$kde_safe_libs +AC_LANG_RESTORE +fi +]) +AC_MSG_RESULT($kde_cv_proto_$1) + +if test "x$kde_cv_func_$1" = xyes; then + AC_DEFINE(HAVE_$5, 1, [Define if you have $1]) + $6 +fi +if test "x$kde_cv_proto_$1" = xno; then + AC_DEFINE(HAVE_$5_PROTO, 1, + [Define if you have the $1 prototype]) +fi + +AH_VERBATIM([_HAVE_$5_PROTO], +[ +#if !defined(HAVE_$5_PROTO) +#ifdef __cplusplus +extern "C" { +#endif +$4; +#ifdef __cplusplus +} +#endif +#endif +]) +]) + +AC_DEFUN([AC_CHECK_SETENV], +[ + KDE_CHECK_FUNC_EXT(setenv, [ +#include +], + [setenv("VAR", "VALUE", 1);], + [int setenv (const char *, const char *, int)], + [SETENV]) +]) + +AC_DEFUN([AC_CHECK_UNSETENV], +[ + KDE_CHECK_FUNC_EXT(unsetenv, [ +#include +], + [unsetenv("VAR");], + [void unsetenv (const char *)], + [UNSETENV]) +]) + +AC_DEFUN([AC_CHECK_GETDOMAINNAME], +[ + KDE_CHECK_FUNC_EXT(getdomainname, [ +#include +#include +#include +], + [ +char buffer[200]; +getdomainname(buffer, 200); +], + [#include + int getdomainname (char *, size_t)], + [GETDOMAINNAME]) +]) + +AC_DEFUN([AC_CHECK_GETHOSTNAME], +[ + KDE_CHECK_FUNC_EXT(gethostname, [ +#include +#include +], + [ +char buffer[200]; +gethostname(buffer, 200); +], + [int gethostname (char *, unsigned int)], + [GETHOSTNAME]) +]) + +AC_DEFUN([AC_CHECK_USLEEP], +[ + KDE_CHECK_FUNC_EXT(usleep, [ +#include +], + [ +usleep(200); +], + [int usleep (unsigned int)], + [USLEEP]) +]) + + +AC_DEFUN([AC_CHECK_RANDOM], +[ + KDE_CHECK_FUNC_EXT(random, [ +#include +], + [ +random(); +], + [long int random(void)], + [RANDOM]) + + KDE_CHECK_FUNC_EXT(srandom, [ +#include +], + [ +srandom(27); +], + [void srandom(unsigned int)], + [SRANDOM]) + +]) + +AC_DEFUN([AC_CHECK_INITGROUPS], +[ + KDE_CHECK_FUNC_EXT(initgroups, [ +#include +#include +#include +], + [ +char buffer[200]; +initgroups(buffer, 27); +], + [int initgroups(const char *, gid_t)], + [INITGROUPS]) +]) + +AC_DEFUN([AC_CHECK_MKSTEMPS], +[ + KDE_CHECK_FUNC_EXT(mkstemps, [ +#include +#include +], + [ +mkstemps("/tmp/aaaXXXXXX", 6); +], + [int mkstemps(char *, int)], + [MKSTEMPS]) +]) + +AC_DEFUN([AC_CHECK_MKSTEMP], +[ + KDE_CHECK_FUNC_EXT(mkstemp, [ +#include +#include +], + [ +mkstemp("/tmp/aaaXXXXXX"); +], + [int mkstemp(char *)], + [MKSTEMP]) +]) + +AC_DEFUN([AC_CHECK_MKDTEMP], +[ + KDE_CHECK_FUNC_EXT(mkdtemp, [ +#include +#include +], + [ +mkdtemp("/tmp/aaaXXXXXX"); +], + [char *mkdtemp(char *)], + [MKDTEMP]) +]) + + +AC_DEFUN([AC_CHECK_RES_INIT], +[ + AC_MSG_CHECKING([if res_init needs -lresolv]) + kde_libs_safe="$LIBS" + LIBS="$LIBS $X_EXTRA_LIBS -lresolv" + AC_TRY_LINK( + [ +#include +#include +#include +#include + ], + [ + res_init(); + ], + [ + LIBRESOLV="-lresolv" + AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_RES_INIT, 1, [Define if you have the res_init function]) + ], + [ AC_MSG_RESULT(no) ] + ) + LIBS=$kde_libs_safe + AC_SUBST(LIBRESOLV) + + KDE_CHECK_FUNC_EXT(res_init, + [ +#include +#include +#include +#include + ], + [res_init()], + [int res_init(void)], + [RES_INIT]) +]) + +AC_DEFUN([AC_CHECK_STRLCPY], +[ + KDE_CHECK_FUNC_EXT(strlcpy, [ +#include +], +[ char buf[20]; + strlcpy(buf, "KDE function test", sizeof(buf)); +], + [unsigned long strlcpy(char*, const char*, unsigned long)], + [STRLCPY]) +]) + +AC_DEFUN([AC_CHECK_STRLCAT], +[ + KDE_CHECK_FUNC_EXT(strlcat, [ +#include +], +[ char buf[20]; + buf[0]='\0'; + strlcat(buf, "KDE function test", sizeof(buf)); +], + [unsigned long strlcat(char*, const char*, unsigned long)], + [STRLCAT]) +]) + +AC_DEFUN([AC_CHECK_RES_QUERY], +[ + KDE_CHECK_FUNC_EXT(res_query, [ +#include +#include +#include +#include +#include +], +[ +res_query(NULL, 0, 0, NULL, 0); +], + [int res_query(const char *, int, int, unsigned char *, int)], + [RES_QUERY]) +]) + +AC_DEFUN([AC_CHECK_DN_SKIPNAME], +[ + KDE_CHECK_FUNC_EXT(dn_skipname, [ +#include +#include +#include +#include +], +[ +dn_skipname (NULL, NULL); +], + [int dn_skipname (unsigned char *, unsigned char *)], + [DN_SKIPNAME]) +]) + + +AC_DEFUN([AC_FIND_GIF], + [AC_MSG_CHECKING([for giflib]) +AC_CACHE_VAL(ac_cv_lib_gif, +[ac_save_LIBS="$LIBS" +if test "x$kde_use_qt_emb" != "xyes" && test "x$kde_use_qt_mac" != "xyes"; then +LIBS="$all_libraries -lgif -lX11 $LIBSOCKET" +else +LIBS="$all_libraries -lgif" +fi +AC_TRY_LINK(dnl +[ +#ifdef __cplusplus +extern "C" { +#endif +int GifLastError(void); +#ifdef __cplusplus +} +#endif +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +], + [return GifLastError();], + eval "ac_cv_lib_gif=yes", + eval "ac_cv_lib_gif=no") +LIBS="$ac_save_LIBS" +])dnl +if eval "test \"`echo $ac_cv_lib_gif`\" = yes"; then + AC_MSG_RESULT(yes) + AC_DEFINE_UNQUOTED(HAVE_LIBGIF, 1, [Define if you have libgif]) +else + AC_MSG_ERROR(You need giflib30. Please install the kdesupport package) +fi +]) + +AC_DEFUN([KDE_FIND_JPEG_HELPER], +[ +AC_MSG_CHECKING([for libjpeg$2]) +AC_CACHE_VAL(ac_cv_lib_jpeg_$1, +[ +ac_save_LIBS="$LIBS" +LIBS="$all_libraries $USER_LDFLAGS -ljpeg$2 -lm" +ac_save_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS $all_includes $USER_INCLUDES" +AC_TRY_LINK( +[/* Override any gcc2 internal prototype to avoid an error. */ +struct jpeg_decompress_struct; +typedef struct jpeg_decompress_struct * j_decompress_ptr; +typedef int size_t; +#ifdef __cplusplus +extern "C" { +#endif + void jpeg_CreateDecompress(j_decompress_ptr cinfo, + int version, size_t structsize); +#ifdef __cplusplus +} +#endif +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +], + [jpeg_CreateDecompress(0L, 0, 0);], + eval "ac_cv_lib_jpeg_$1=-ljpeg$2", + eval "ac_cv_lib_jpeg_$1=no") +LIBS="$ac_save_LIBS" +CFLAGS="$ac_save_CFLAGS" +]) + +if eval "test ! \"`echo $ac_cv_lib_jpeg_$1`\" = no"; then + LIBJPEG="$ac_cv_lib_jpeg_$1" + AC_MSG_RESULT($ac_cv_lib_jpeg_$1) +else + AC_MSG_RESULT(no) + $3 +fi + +]) + +AC_DEFUN([AC_FIND_JPEG], +[ +dnl first look for libraries +KDE_FIND_JPEG_HELPER(6b, 6b, + KDE_FIND_JPEG_HELPER(normal, [], + [ + LIBJPEG= + ] + ) +) + +dnl then search the headers (can't use simply AC_TRY_xxx, as jpeglib.h +dnl requires system dependent includes loaded before it) +jpeg_incdirs="$includedir /usr/include /usr/local/include $kde_extra_includes" +AC_FIND_FILE(jpeglib.h, $jpeg_incdirs, jpeg_incdir) +test "x$jpeg_incdir" = xNO && jpeg_incdir= + +dnl if headers _and_ libraries are missing, this is no error, and we +dnl continue with a warning (the user will get no jpeg support in khtml) +dnl if only one is missing, it means a configuration error, but we still +dnl only warn +if test -n "$jpeg_incdir" && test -n "$LIBJPEG" ; then + AC_DEFINE_UNQUOTED(HAVE_LIBJPEG, 1, [Define if you have libjpeg]) +else + if test -n "$jpeg_incdir" || test -n "$LIBJPEG" ; then + AC_MSG_WARN([ +There is an installation error in jpeg support. You seem to have only one +of either the headers _or_ the libraries installed. You may need to either +provide correct --with-extra-... options, or the development package of +libjpeg6b. You can get a source package of libjpeg from http://www.ijg.org/ +Disabling JPEG support. +]) + else + AC_MSG_WARN([libjpeg not found. disable JPEG support.]) + fi + jpeg_incdir= + LIBJPEG= +fi + +AC_SUBST(LIBJPEG) +AH_VERBATIM(_AC_CHECK_JPEG, +[/* + * jpeg.h needs HAVE_BOOLEAN, when the system uses boolean in system + * headers and I'm too lazy to write a configure test as long as only + * unixware is related + */ +#ifdef _UNIXWARE +#define HAVE_BOOLEAN +#endif +]) +]) + +AC_DEFUN([KDE_CHECK_QT_JPEG], +[ +if test -n "$LIBJPEG"; then +AC_MSG_CHECKING([if Qt needs $LIBJPEG]) +AC_CACHE_VAL(kde_cv_qt_jpeg, +[ +AC_LANG_SAVE +AC_LANG_CPLUSPLUS +ac_save_LIBS="$LIBS" +LIBS="$all_libraries $USER_LDFLAGS $LIBQT" +LIBS=`echo $LIBS | sed "s/$LIBJPEG//"` +ac_save_CXXFLAGS="$CXXFLAGS" +CXXFLAGS="$CXXFLAGS $all_includes $USER_INCLUDES" +AC_TRY_LINK( +[#include ], + [ + int argc; + char** argv; + QApplication app(argc, argv);], + eval "kde_cv_qt_jpeg=no", + eval "kde_cv_qt_jpeg=yes") +LIBS="$ac_save_LIBS" +CXXFLAGS="$ac_save_CXXFLAGS" +AC_LANG_RESTORE +fi +]) + +if eval "test ! \"`echo $kde_cv_qt_jpeg`\" = no"; then + AC_MSG_RESULT(yes) + LIBJPEG_QT='$(LIBJPEG)' +else + AC_MSG_RESULT(no) + LIBJPEG_QT= +fi + +]) + +AC_DEFUN([AC_FIND_ZLIB], +[ +AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) +AC_MSG_CHECKING([for libz]) +AC_CACHE_VAL(ac_cv_lib_z, +[ +kde_save_LIBS="$LIBS" +LIBS="$all_libraries $USER_LDFLAGS -lz $LIBSOCKET" +kde_save_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS $all_includes $USER_INCLUDES" +AC_TRY_LINK(dnl +[ +#include +], +[ + char buf[42]; + gzFile f = (gzFile) 0; + /* this would segfault.. but we only link, don't run */ + (void) gzgets(f, buf, sizeof(buf)); + + return (zlibVersion() == ZLIB_VERSION); +], + eval "ac_cv_lib_z='-lz'", + eval "ac_cv_lib_z=no") +LIBS="$kde_save_LIBS" +CFLAGS="$kde_save_CFLAGS" +])dnl +if test ! "$ac_cv_lib_z" = no; then + AC_DEFINE_UNQUOTED(HAVE_LIBZ, 1, [Define if you have libz]) + LIBZ="$ac_cv_lib_z" + AC_MSG_RESULT($ac_cv_lib_z) +else + AC_MSG_ERROR(not found. + Possibly configure picks up an outdated version + installed by XFree86. Remove it from your system. + + Check your installation and look into config.log) + LIBZ="" +fi +AC_SUBST(LIBZ) +]) + +AC_DEFUN([KDE_TRY_TIFFLIB], +[ +AC_MSG_CHECKING([for libtiff $1]) + +AC_CACHE_VAL(kde_cv_libtiff_$1, +[ +AC_LANG_SAVE +AC_LANG_CPLUSPLUS +kde_save_LIBS="$LIBS" +if test "x$kde_use_qt_emb" != "xyes" && test "x$kde_use_qt_mac" != "xyes"; then +LIBS="$all_libraries $USER_LDFLAGS -l$1 $LIBJPEG $LIBZ -lX11 $LIBSOCKET -lm" +else +LIBS="$all_libraries $USER_LDFLAGS -l$1 $LIBJPEG $LIBZ -lm" +fi +kde_save_CXXFLAGS="$CXXFLAGS" +CXXFLAGS="$CXXFLAGS $all_includes $USER_INCLUDES" + +AC_TRY_LINK(dnl +[ +#include +], + [return (TIFFOpen( "", "r") == 0); ], +[ + kde_cv_libtiff_$1="-l$1 $LIBJPEG $LIBZ" +], [ + kde_cv_libtiff_$1=no +]) + +LIBS="$kde_save_LIBS" +CXXFLAGS="$kde_save_CXXFLAGS" +AC_LANG_RESTORE +]) + +if test "$kde_cv_libtiff_$1" = "no"; then + AC_MSG_RESULT(no) + LIBTIFF="" + $3 +else + LIBTIFF="$kde_cv_libtiff_$1" + AC_MSG_RESULT(yes) + AC_DEFINE_UNQUOTED(HAVE_LIBTIFF, 1, [Define if you have libtiff]) + $2 +fi + +]) + +AC_DEFUN([AC_FIND_TIFF], +[ +AC_REQUIRE([K_PATH_X]) +AC_REQUIRE([AC_FIND_ZLIB]) +AC_REQUIRE([AC_FIND_JPEG]) +AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) + +KDE_TRY_TIFFLIB(tiff, [], + KDE_TRY_TIFFLIB(tiff34)) + +AC_SUBST(LIBTIFF) +]) + + +AC_DEFUN([AC_FIND_PNG], +[ +AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) +AC_REQUIRE([AC_FIND_ZLIB]) +AC_MSG_CHECKING([for libpng]) +AC_CACHE_VAL(ac_cv_lib_png, +[ +kde_save_LIBS="$LIBS" +if test "x$kde_use_qt_emb" != "xyes" && test "x$kde_use_qt_mac" != "xyes"; then +LIBS="$LIBS $all_libraries $USER_LDFLAGS -lpng $LIBZ -lm -lX11 $LIBSOCKET" +else +LIBS="$LIBS $all_libraries $USER_LDFLAGS -lpng $LIBZ -lm" +fi +kde_save_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS $all_includes $USER_INCLUDES" + +AC_TRY_LINK(dnl + [ + #include + ], + [ + png_structp png_ptr = png_create_read_struct( /* image ptr */ + PNG_LIBPNG_VER_STRING, 0, 0, 0 ); + return( png_ptr != 0 ); + ], + eval "ac_cv_lib_png='-lpng $LIBZ -lm'", + eval "ac_cv_lib_png=no" +) +LIBS="$kde_save_LIBS" +CFLAGS="$kde_save_CFLAGS" +])dnl +if eval "test ! \"`echo $ac_cv_lib_png`\" = no"; then + AC_DEFINE_UNQUOTED(HAVE_LIBPNG, 1, [Define if you have libpng]) + LIBPNG="$ac_cv_lib_png" + AC_SUBST(LIBPNG) + AC_MSG_RESULT($ac_cv_lib_png) +else + AC_MSG_RESULT(no) + LIBPNG="" + AC_SUBST(LIBPNG) +fi +]) + + +AC_DEFUN([AC_FIND_JASPER], +[ +AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) +AC_REQUIRE([AC_FIND_JPEG]) +AC_MSG_CHECKING([for jasper]) +AC_CACHE_VAL(ac_cv_jasper, +[ +kde_save_LIBS="$LIBS" +LIBS="$LIBS $all_libraries $USER_LDFLAGS -ljasper $LIBJPEG -lm" +kde_save_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS $all_includes $USER_INCLUDES" + +AC_TRY_LINK(dnl + [ + #include + ], + [ + return( jas_init() ); + ], + eval "ac_cv_jasper='-ljasper $LIBJPEG -lm'", + eval "ac_cv_jasper=no" +) +LIBS="$kde_save_LIBS" +CFLAGS="$kde_save_CFLAGS" +])dnl +if eval "test ! \"`echo $ac_cv_jasper`\" = no"; then + AC_DEFINE_UNQUOTED(HAVE_JASPER, 1, [Define if you have jasper]) + LIB_JASPER="$ac_cv_jasper" + AC_MSG_RESULT($ac_cv_jasper) +else + AC_MSG_RESULT(no) + LIB_JASPER="" +fi +AC_SUBST(LIB_JASPER) +]) + +AC_DEFUN([AC_CHECK_BOOL], +[ + AC_DEFINE_UNQUOTED(HAVE_BOOL, 1, [You _must_ have bool]) +]) + +AC_DEFUN([AC_CHECK_GNU_EXTENSIONS], +[ +AC_MSG_CHECKING(if you need GNU extensions) +AC_CACHE_VAL(ac_cv_gnu_extensions, +[ +cat > conftest.c << EOF +#include + +#ifdef __GNU_LIBRARY__ +yes +#endif +EOF + +if (eval "$ac_cpp conftest.c") 2>&5 | + egrep "yes" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_gnu_extensions=yes +else + ac_cv_gnu_extensions=no +fi +]) + +AC_MSG_RESULT($ac_cv_gnu_extensions) +if test "$ac_cv_gnu_extensions" = "yes"; then + AC_DEFINE_UNQUOTED(_GNU_SOURCE, 1, [Define if you need to use the GNU extensions]) +fi +]) + +AC_DEFUN([KDE_CHECK_COMPILER_FLAG], +[ +AC_MSG_CHECKING([whether $CXX supports -$1]) +kde_cache=`echo $1 | sed 'y% .=/+-,%____p__%'` +AC_CACHE_VAL(kde_cv_prog_cxx_$kde_cache, +[ + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS -$1" + AC_TRY_LINK([],[ return 0; ], [eval "kde_cv_prog_cxx_$kde_cache=yes"], []) + CXXFLAGS="$save_CXXFLAGS" + AC_LANG_RESTORE +]) +if eval "test \"`echo '$kde_cv_prog_cxx_'$kde_cache`\" = yes"; then + AC_MSG_RESULT(yes) + : + $2 +else + AC_MSG_RESULT(no) + : + $3 +fi +]) + +AC_DEFUN([KDE_CHECK_C_COMPILER_FLAG], +[ +AC_MSG_CHECKING([whether $CC supports -$1]) +kde_cache=`echo $1 | sed 'y% .=/+-,%____p__%'` +AC_CACHE_VAL(kde_cv_prog_cc_$kde_cache, +[ + AC_LANG_SAVE + AC_LANG_C + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -$1" + AC_TRY_LINK([],[ return 0; ], [eval "kde_cv_prog_cc_$kde_cache=yes"], []) + CFLAGS="$save_CFLAGS" + AC_LANG_RESTORE +]) +if eval "test \"`echo '$kde_cv_prog_cc_'$kde_cache`\" = yes"; then + AC_MSG_RESULT(yes) + : + $2 +else + AC_MSG_RESULT(no) + : + $3 +fi +]) + + +dnl AC_REMOVE_FORBIDDEN removes forbidden arguments from variables +dnl use: AC_REMOVE_FORBIDDEN(CC, [-forbid -bad-option whatever]) +dnl it's all white-space separated +AC_DEFUN([AC_REMOVE_FORBIDDEN], +[ __val=$$1 + __forbid=" $2 " + if test -n "$__val"; then + __new="" + ac_save_IFS=$IFS + IFS=" " + for i in $__val; do + case "$__forbid" in + *" $i "*) AC_MSG_WARN([found forbidden $i in $1, removing it]) ;; + *) # Careful to not add spaces, where there were none, because otherwise + # libtool gets confused, if we change e.g. CXX + if test -z "$__new" ; then __new=$i ; else __new="$__new $i" ; fi ;; + esac + done + IFS=$ac_save_IFS + $1=$__new + fi +]) + +dnl AC_VALIDIFY_CXXFLAGS checks for forbidden flags the user may have given +AC_DEFUN([AC_VALIDIFY_CXXFLAGS], +[dnl +if test "x$kde_use_qt_emb" != "xyes"; then + AC_REMOVE_FORBIDDEN(CXX, [-fno-rtti -rpath]) + AC_REMOVE_FORBIDDEN(CXXFLAGS, [-fno-rtti -rpath]) +else + AC_REMOVE_FORBIDDEN(CXX, [-rpath]) + AC_REMOVE_FORBIDDEN(CXXFLAGS, [-rpath]) +fi +]) + +AC_DEFUN([AC_CHECK_COMPILERS], +[ + AC_ARG_ENABLE(debug, + AC_HELP_STRING([--enable-debug=ARG],[enables debug symbols (yes|no|full) [default=no]]), + [ + case $enableval in + yes) + kde_use_debug_code="yes" + kde_use_debug_define=no + ;; + full) + kde_use_debug_code="full" + kde_use_debug_define=no + ;; + *) + kde_use_debug_code="no" + kde_use_debug_define=yes + ;; + esac + ], + [kde_use_debug_code="no" + kde_use_debug_define=no + ]) + + dnl Just for configure --help + AC_ARG_ENABLE(dummyoption, + AC_HELP_STRING([--disable-debug], + [disables debug output and debug symbols [default=no]]), + [],[]) + + AC_ARG_ENABLE(strict, + AC_HELP_STRING([--enable-strict], + [compiles with strict compiler options (may not work!)]), + [ + if test $enableval = "no"; then + kde_use_strict_options="no" + else + kde_use_strict_options="yes" + fi + ], [kde_use_strict_options="no"]) + + AC_ARG_ENABLE(warnings,AC_HELP_STRING([--disable-warnings],[disables compilation with -Wall and similar]), + [ + if test $enableval = "no"; then + kde_use_warnings="no" + else + kde_use_warnings="yes" + fi + ], [kde_use_warnings="yes"]) + + dnl enable warnings for debug build + if test "$kde_use_debug_code" != "no"; then + kde_use_warnings=yes + fi + + AC_ARG_ENABLE(profile,AC_HELP_STRING([--enable-profile],[creates profiling infos [default=no]]), + [kde_use_profiling=$enableval], + [kde_use_profiling="no"] + ) + + dnl this prevents stupid AC_PROG_CC to add "-g" to the default CFLAGS + CFLAGS=" $CFLAGS" + + AC_PROG_CC + + AC_PROG_CPP + + if test "$GCC" = "yes"; then + if test "$kde_use_debug_code" != "no"; then + if test $kde_use_debug_code = "full"; then + CFLAGS="-g3 -fno-inline $CFLAGS" + else + CFLAGS="-g -O2 $CFLAGS" + fi + else + CFLAGS="-O2 $CFLAGS" + fi + fi + + if test "$kde_use_debug_define" = "yes"; then + CFLAGS="-DNDEBUG $CFLAGS" + fi + + + case "$host" in + *-*-sysv4.2uw*) CFLAGS="-D_UNIXWARE $CFLAGS";; + *-*-sysv5uw7*) CFLAGS="-D_UNIXWARE7 $CFLAGS";; + esac + + if test -z "$LDFLAGS" && test "$kde_use_debug_code" = "no" && test "$GCC" = "yes"; then + LDFLAGS="" + fi + + CXXFLAGS=" $CXXFLAGS" + + AC_PROG_CXX + + if test "$GXX" = "yes" || test "$CXX" = "KCC"; then + if test "$kde_use_debug_code" != "no"; then + if test "$CXX" = "KCC"; then + CXXFLAGS="+K0 -Wall -pedantic -W -Wpointer-arith -Wwrite-strings $CXXFLAGS" + else + if test "$kde_use_debug_code" = "full"; then + CXXFLAGS="-g3 -fno-inline $CXXFLAGS" + else + CXXFLAGS="-g -O2 $CXXFLAGS" + fi + fi + KDE_CHECK_COMPILER_FLAG(fno-builtin,[CXXFLAGS="-fno-builtin $CXXFLAGS"]) + + dnl convenience compiler flags + KDE_CHECK_COMPILER_FLAG(Woverloaded-virtual, [WOVERLOADED_VIRTUAL="-Woverloaded-virtual"], [WOVERLOADED_VRITUAL=""]) + AC_SUBST(WOVERLOADED_VIRTUAL) + else + if test "$CXX" = "KCC"; then + CXXFLAGS="+K3 $CXXFLAGS" + else + CXXFLAGS="-O2 $CXXFLAGS" + fi + fi + fi + + if test "$kde_use_debug_define" = "yes"; then + CXXFLAGS="-DNDEBUG -DNO_DEBUG $CXXFLAGS" + fi + + if test "$kde_use_profiling" = "yes"; then + KDE_CHECK_COMPILER_FLAG(pg, + [ + CFLAGS="-pg $CFLAGS" + CXXFLAGS="-pg $CXXFLAGS" + ]) + fi + + if test "$kde_use_warnings" = "yes"; then + if test "$GCC" = "yes"; then + CXXFLAGS="-Wall -W -Wpointer-arith -Wwrite-strings $CXXFLAGS" + case $host in + *-*-linux-gnu) + CFLAGS="-ansi -W -Wall -Wchar-subscripts -Wshadow -Wpointer-arith -Wmissing-prototypes -Wwrite-strings -D_XOPEN_SOURCE=500 -D_BSD_SOURCE $CFLAGS" + CXXFLAGS="-ansi -D_XOPEN_SOURCE=500 -D_BSD_SOURCE -Wcast-align -Wconversion -Wchar-subscripts $CXXFLAGS" + KDE_CHECK_COMPILER_FLAG(Wmissing-format-attribute, [CXXFLAGS="$CXXFLAGS -Wformat-security -Wmissing-format-attribute"]) + KDE_CHECK_C_COMPILER_FLAG(Wmissing-format-attribute, [CFLAGS="$CFLAGS -Wformat-security -Wmissing-format-attribute"]) + ;; + esac + KDE_CHECK_COMPILER_FLAG(Wundef,[CXXFLAGS="-Wundef $CXXFLAGS"]) + KDE_CHECK_COMPILER_FLAG(Wno-long-long,[CXXFLAGS="-Wno-long-long $CXXFLAGS"]) + KDE_CHECK_COMPILER_FLAG(Wnon-virtual-dtor,[CXXFLAGS="-Wnon-virtual-dtor $CXXFLAGS"]) + fi + fi + + if test "$GXX" = "yes" && test "$kde_use_strict_options" = "yes"; then + CXXFLAGS="-Wcast-qual -Wshadow -Wcast-align $CXXFLAGS" + fi + + AC_ARG_ENABLE(pch, + AC_HELP_STRING([--enable-pch], + [enables precompiled header support (currently only KCC or gcc >=3.4+unsermake) [default=no]]), + [ kde_use_pch=$enableval ],[ kde_use_pch=no ]) + + HAVE_GCC_VISIBILITY=0 + AC_SUBST([HAVE_GCC_VISIBILITY]) + + if test "$GXX" = "yes"; then + KDE_CHECK_COMPILER_FLAG(fno-exceptions,[CXXFLAGS="$CXXFLAGS -fno-exceptions"]) + KDE_CHECK_COMPILER_FLAG(fno-check-new, [CXXFLAGS="$CXXFLAGS -fno-check-new"]) + KDE_CHECK_COMPILER_FLAG(fno-common, [CXXFLAGS="$CXXFLAGS -fno-common"]) + KDE_CHECK_COMPILER_FLAG(fexceptions, [USE_EXCEPTIONS="-fexceptions"], USE_EXCEPTIONS= ) + ENABLE_PERMISSIVE_FLAG="-fpermissive" + + if test "$kde_use_pch" = "yes"; then + AC_MSG_CHECKING(whether gcc supports precompiling c header files) + echo >conftest.h + if $CC -x c-header conftest.h >/dev/null 2>/dev/null; then + kde_gcc_supports_pch=yes + AC_MSG_RESULT(yes) + else + kde_gcc_supports_pch=no + AC_MSG_RESULT(no) + fi + if test "$kde_gcc_supports_pch" = "yes"; then + AC_MSG_CHECKING(whether gcc supports precompiling c++ header files) + if $CXX -x c++-header conftest.h >/dev/null 2>/dev/null; then + kde_gcc_supports_pch=yes + AC_MSG_RESULT(yes) + else + kde_gcc_supports_pch=no + AC_MSG_RESULT(no) + fi + fi + rm -f conftest.h conftest.h.gch + fi + AM_CONDITIONAL(unsermake_enable_pch, test "$kde_use_pch" = "yes" && test "$kde_gcc_supports_pch" = "yes") + fi + if test "$CXX" = "KCC"; then + dnl unfortunately we currently cannot disable exception support in KCC + dnl because doing so is binary incompatible and Qt by default links with exceptions :-( + dnl KDE_CHECK_COMPILER_FLAG(-no_exceptions,[CXXFLAGS="$CXXFLAGS --no_exceptions"]) + dnl KDE_CHECK_COMPILER_FLAG(-exceptions, [USE_EXCEPTIONS="--exceptions"], USE_EXCEPTIONS= ) + + if test "$kde_use_pch" = "yes"; then + dnl TODO: support --pch-dir! + KDE_CHECK_COMPILER_FLAG(-pch,[CXXFLAGS="$CXXFLAGS --pch"]) + dnl the below works (but the dir must exist), but it's + dnl useless for a whole package. + dnl The are precompiled headers for each source file, so when compiling + dnl from scratch, it doesn't make a difference, and they take up + dnl around ~5Mb _per_ sourcefile. + dnl KDE_CHECK_COMPILER_FLAG(-pch_dir /tmp, + dnl [CXXFLAGS="$CXXFLAGS --pch_dir `pwd`/pcheaders"]) + fi + dnl this flag controls inlining. by default KCC inlines in optimisation mode + dnl all implementations that are defined inside the class {} declaration. + dnl because of templates-compatibility with broken gcc compilers, this + dnl can cause excessive inlining. This flag limits it to a sane level + KDE_CHECK_COMPILER_FLAG(-inline_keyword_space_time=6,[CXXFLAGS="$CXXFLAGS --inline_keyword_space_time=6"]) + KDE_CHECK_COMPILER_FLAG(-inline_auto_space_time=2,[CXXFLAGS="$CXXFLAGS --inline_auto_space_time=2"]) + KDE_CHECK_COMPILER_FLAG(-inline_implicit_space_time=2.0,[CXXFLAGS="$CXXFLAGS --inline_implicit_space_time=2.0"]) + KDE_CHECK_COMPILER_FLAG(-inline_generated_space_time=2.0,[CXXFLAGS="$CXXFLAGS --inline_generated_space_time=2.0"]) + dnl Some source files are shared between multiple executables + dnl (or libraries) and some of those need template instantiations. + dnl In that case KCC needs to compile those sources with + dnl --one_instantiation_per_object. To make it easy for us we compile + dnl _all_ objects with that flag (--one_per is a shorthand). + KDE_CHECK_COMPILER_FLAG(-one_per, [CXXFLAGS="$CXXFLAGS --one_per"]) + fi + AC_SUBST(USE_EXCEPTIONS) + dnl obsolete macro - provided to keep things going + USE_RTTI= + AC_SUBST(USE_RTTI) + + case "$host" in + *-*-irix*) test "$GXX" = yes && CXXFLAGS="-D_LANGUAGE_C_PLUS_PLUS -D__LANGUAGE_C_PLUS_PLUS $CXXFLAGS" ;; + *-*-sysv4.2uw*) CXXFLAGS="-D_UNIXWARE $CXXFLAGS";; + *-*-sysv5uw7*) CXXFLAGS="-D_UNIXWARE7 $CXXFLAGS";; + *-*-solaris*) + if test "$GXX" = yes; then + libstdcpp=`$CXX -print-file-name=libstdc++.so` + if test ! -f $libstdcpp; then + AC_MSG_ERROR([You've compiled gcc without --enable-shared. This doesn't work with KDE. Please recompile gcc with --enable-shared to receive a libstdc++.so]) + fi + fi + ;; + esac + + AC_VALIDIFY_CXXFLAGS + + AC_PROG_CXXCPP + + if test "$GCC" = yes; then + NOOPT_CFLAGS=-O0 + fi + KDE_CHECK_COMPILER_FLAG(O0,[NOOPT_CXXFLAGS=-O0]) + + AC_ARG_ENABLE(coverage, + AC_HELP_STRING([--enable-coverage],[use gcc coverage testing]), [ + if test "$am_cv_CC_dependencies_compiler_type" = "gcc3"; then + ac_coverage_compiler="-fprofile-arcs -ftest-coverage" + ac_coverage_linker="-lgcc" + elif test "$am_cv_CC_dependencies_compiler_type" = "gcc"; then + ac_coverage_compiler="-fprofile-arcs -ftest-coverage" + ac_coverage_linker="" + else + AC_MSG_ERROR([coverage with your compiler is not supported]) + fi + CFLAGS="$CFLAGS $ac_coverage_compiler" + CXXFLAGS="$CXXFLAGS $ac_coverage_compiler" + LDFLAGS="$LDFLAGS $ac_coverage_linker" + ]) + + AC_SUBST(NOOPT_CXXFLAGS) + AC_SUBST(NOOPT_CFLAGS) + AC_SUBST(ENABLE_PERMISSIVE_FLAG) + + KDE_CHECK_NEW_LDFLAGS + KDE_CHECK_FINAL + KDE_CHECK_CLOSURE + KDE_CHECK_NMCHECK + + ifdef([AM_DEPENDENCIES], AC_REQUIRE([KDE_ADD_DEPENDENCIES]), []) +]) + +AC_DEFUN([KDE_CHECK_AND_ADD_HIDDEN_VISIBILITY], +[ + if test "$GXX" = "yes"; then + KDE_CHECK_COMPILER_FLAG(fno-exceptions,[CXXFLAGS="$CXXFLAGS -fno-exceptions"]) + KDE_CHECK_COMPILER_FLAG(fno-check-new, [CXXFLAGS="$CXXFLAGS -fno-check-new"]) + KDE_CHECK_COMPILER_FLAG(fno-common, [CXXFLAGS="$CXXFLAGS -fno-common"]) + KDE_CHECK_COMPILER_FLAG(fvisibility=hidden, + [ + CXXFLAGS="$CXXFLAGS -fvisibility=hidden -fvisibility-inlines-hidden" + HAVE_GCC_VISIBILITY=1 + AC_DEFINE_UNQUOTED(__KDE_HAVE_GCC_VISIBILITY, "$HAVE_GCC_VISIBILITY", [define to 1 if -fvisibility is supported]) + ]) + fi +]) + +AC_DEFUN([KDE_ENABLE_HIDDEN_VISIBILITY], +[ + AC_REQUIRE([KDE_CHECK_AND_ADD_HIDDEN_VISIBILITY]) +]) + +AC_DEFUN([KDE_ADD_DEPENDENCIES], +[ + [A]M_DEPENDENCIES(CC) + [A]M_DEPENDENCIES(CXX) +]) + +dnl just a wrapper to clean up configure.in +AC_DEFUN([KDE_PROG_LIBTOOL], +[ +AC_REQUIRE([AC_CHECK_COMPILERS]) +AC_REQUIRE([AC_ENABLE_SHARED]) +AC_REQUIRE([AC_ENABLE_STATIC]) + +AC_REQUIRE([AC_LIBTOOL_DLOPEN]) +AC_REQUIRE([KDE_CHECK_LIB64]) + +AC_OBJEXT +AC_EXEEXT + +AM_PROG_LIBTOOL +AC_LIBTOOL_CXX + +LIBTOOL_SHELL="/bin/sh ./libtool" +# LIBTOOL="$LIBTOOL --silent" +KDE_PLUGIN="-avoid-version -module -no-undefined \$(KDE_NO_UNDEFINED) \$(KDE_RPATH) \$(KDE_MT_LDFLAGS)" +AC_SUBST(KDE_PLUGIN) + +# we patch configure quite some so we better keep that consistent for incremental runs +AC_SUBST(AUTOCONF,'$(SHELL) $(top_srcdir)/admin/cvs.sh configure || touch configure') +]) + +AC_DEFUN([KDE_CHECK_LIB64], +[ + kdelibsuff="$kde_libs_suffix" + if test -z "$kdelibsuff"; then + kdelibsuff=no + fi + AC_ARG_ENABLE(libsuffix, + AC_HELP_STRING([--enable-libsuffix], + [/lib directory suffix (64,32,none[=default])]), + kdelibsuff=$enableval) + # TODO: add an auto case that compiles a little C app to check + # where the glibc is + if test "$kdelibsuff" = "no"; then + kdelibsuff= + fi + if test -z "$kdelibsuff"; then + AC_MSG_RESULT([not using lib directory suffix]) + AC_DEFINE(KDELIBSUFF, [""], Suffix for lib directories) + else + if test "$libdir" = '${exec_prefix}/lib'; then + libdir="$libdir${kdelibsuff}" + AC_SUBST([libdir], ["$libdir"]) dnl ugly hack for lib64 platforms + fi + AC_DEFINE_UNQUOTED(KDELIBSUFF, ["${kdelibsuff}"], Suffix for lib directories) + AC_MSG_RESULT([using lib directory suffix $kdelibsuff]) + fi +]) + +AC_DEFUN([KDE_CHECK_TYPES], +[ AC_CHECK_SIZEOF(int, 4)dnl + AC_CHECK_SIZEOF(short)dnl + AC_CHECK_SIZEOF(long, 4)dnl + AC_CHECK_SIZEOF(char *, 4)dnl +])dnl + +dnl Not used - kept for compat only? +AC_DEFUN([KDE_DO_IT_ALL], +[ +AC_CANONICAL_SYSTEM +AC_ARG_PROGRAM +AM_INIT_AUTOMAKE($1, $2) +AM_DISABLE_LIBRARIES +AC_PREFIX_DEFAULT(${KDEDIR:-/usr/local/kde}) +AC_CHECK_COMPILERS +KDE_PROG_LIBTOOL +AM_KDE_WITH_NLS +AC_PATH_KDE +]) + +AC_DEFUN([AC_CHECK_RPATH], +[ +AC_MSG_CHECKING(for rpath) +AC_ARG_ENABLE(rpath, + AC_HELP_STRING([--disable-rpath],[do not use the rpath feature of ld]), + USE_RPATH=$enableval, USE_RPATH=yes) + +if test -z "$KDE_RPATH" && test "$USE_RPATH" = "yes"; then + + KDE_RPATH="-R \$(libdir)" + + if test "$kde_libraries" != "$libdir"; then + KDE_RPATH="$KDE_RPATH -R \$(kde_libraries)" + fi + + if test -n "$qt_libraries"; then + KDE_RPATH="$KDE_RPATH -R \$(qt_libraries)" + fi + dnl $x_libraries is set to /usr/lib in case + if test -n "$X_LDFLAGS"; then + X_RPATH="-R \$(x_libraries)" + KDE_RPATH="$KDE_RPATH $X_RPATH" + fi + if test -n "$KDE_EXTRA_RPATH"; then + KDE_RPATH="$KDE_RPATH \$(KDE_EXTRA_RPATH)" + fi +fi +AC_SUBST(KDE_EXTRA_RPATH) +AC_SUBST(KDE_RPATH) +AC_SUBST(X_RPATH) +AC_MSG_RESULT($USE_RPATH) +]) + +dnl Check for the type of the third argument of getsockname +AC_DEFUN([AC_CHECK_SOCKLEN_T], +[ + AC_MSG_CHECKING(for socklen_t) + AC_CACHE_VAL(kde_cv_socklen_t, + [ + AC_LANG_PUSH(C++) + kde_cv_socklen_t=no + AC_TRY_COMPILE([ + #include + #include + ], + [ + socklen_t len; + getpeername(0,0,&len); + ], + [ + kde_cv_socklen_t=yes + kde_cv_socklen_t_equiv=socklen_t + ]) + AC_LANG_POP(C++) + ]) + AC_MSG_RESULT($kde_cv_socklen_t) + if test $kde_cv_socklen_t = no; then + AC_MSG_CHECKING([for socklen_t equivalent for socket functions]) + AC_CACHE_VAL(kde_cv_socklen_t_equiv, + [ + kde_cv_socklen_t_equiv=int + AC_LANG_PUSH(C++) + for t in int size_t unsigned long "unsigned long"; do + AC_TRY_COMPILE([ + #include + #include + ], + [ + $t len; + getpeername(0,0,&len); + ], + [ + kde_cv_socklen_t_equiv="$t" + break + ]) + done + AC_LANG_POP(C++) + ]) + AC_MSG_RESULT($kde_cv_socklen_t_equiv) + fi + AC_DEFINE_UNQUOTED(kde_socklen_t, $kde_cv_socklen_t_equiv, + [type to use in place of socklen_t if not defined]) + AC_DEFINE_UNQUOTED(ksize_t, $kde_cv_socklen_t_equiv, + [type to use in place of socklen_t if not defined (deprecated, use kde_socklen_t)]) +]) + +dnl This is a merge of some macros out of the gettext aclocal.m4 +dnl since we don't need anything, I took the things we need +dnl the copyright for them is: +dnl > +dnl Copyright (C) 1994, 1995, 1996, 1997, 1998 Free Software Foundation, Inc. +dnl This Makefile.in is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl This program is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY, to the extent permitted by law; without +dnl even the implied warranty of MERCHANTABILITY or FITNESS FOR A +dnl PARTICULAR PURPOSE. +dnl > +dnl for this file it is relicensed under LGPL + +AC_DEFUN([AM_KDE_WITH_NLS], + [ + dnl If we use NLS figure out what method + + AM_PATH_PROG_WITH_TEST_KDE(MSGFMT, msgfmt, + [test -n "`$ac_dir/$ac_word --version 2>&1 | grep 'GNU gettext'`"], msgfmt) + AC_PATH_PROG(GMSGFMT, gmsgfmt, $MSGFMT) + + if test -z "`$GMSGFMT --version 2>&1 | grep 'GNU gettext'`"; then + AC_MSG_RESULT([found msgfmt program is not GNU msgfmt; ignore it]) + GMSGFMT=":" + fi + MSGFMT=$GMSGFMT + AC_SUBST(GMSGFMT) + AC_SUBST(MSGFMT) + + AM_PATH_PROG_WITH_TEST_KDE(XGETTEXT, xgettext, + [test -z "`$ac_dir/$ac_word -h 2>&1 | grep '(HELP)'`"], :) + + dnl Test whether we really found GNU xgettext. + if test "$XGETTEXT" != ":"; then + dnl If it is no GNU xgettext we define it as : so that the + dnl Makefiles still can work. + if $XGETTEXT --omit-header /dev/null 2> /dev/null; then + : ; + else + AC_MSG_RESULT( + [found xgettext programs is not GNU xgettext; ignore it]) + XGETTEXT=":" + fi + fi + AC_SUBST(XGETTEXT) + + ]) + +# Search path for a program which passes the given test. +# Ulrich Drepper , 1996. + +# serial 1 +# Stephan Kulow: I appended a _KDE against name conflicts + +dnl AM_PATH_PROG_WITH_TEST_KDE(VARIABLE, PROG-TO-CHECK-FOR, +dnl TEST-PERFORMED-ON-FOUND_PROGRAM [, VALUE-IF-NOT-FOUND [, PATH]]) +AC_DEFUN([AM_PATH_PROG_WITH_TEST_KDE], +[# Extract the first word of "$2", so it can be a program name with args. +set dummy $2; ac_word=[$]2 +AC_MSG_CHECKING([for $ac_word]) +AC_CACHE_VAL(ac_cv_path_$1, +[case "[$]$1" in + /*) + ac_cv_path_$1="[$]$1" # Let the user override the test with a path. + ;; + *) + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in ifelse([$5], , $PATH, [$5]); do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + if [$3]; then + ac_cv_path_$1="$ac_dir/$ac_word" + break + fi + fi + done + IFS="$ac_save_ifs" +dnl If no 4th arg is given, leave the cache variable unset, +dnl so AC_PATH_PROGS will keep looking. +ifelse([$4], , , [ test -z "[$]ac_cv_path_$1" && ac_cv_path_$1="$4" +])dnl + ;; +esac])dnl +$1="$ac_cv_path_$1" +if test -n "[$]$1"; then + AC_MSG_RESULT([$]$1) +else + AC_MSG_RESULT(no) +fi +AC_SUBST($1)dnl +]) + + +# Check whether LC_MESSAGES is available in . +# Ulrich Drepper , 1995. + +# serial 1 + +AC_DEFUN([AM_LC_MESSAGES], + [if test $ac_cv_header_locale_h = yes; then + AC_CACHE_CHECK([for LC_MESSAGES], am_cv_val_LC_MESSAGES, + [AC_TRY_LINK([#include ], [return LC_MESSAGES], + am_cv_val_LC_MESSAGES=yes, am_cv_val_LC_MESSAGES=no)]) + if test $am_cv_val_LC_MESSAGES = yes; then + AC_DEFINE(HAVE_LC_MESSAGES, 1, [Define if your locale.h file contains LC_MESSAGES]) + fi + fi]) + +dnl From Jim Meyering. +dnl FIXME: migrate into libit. + +AC_DEFUN([AM_FUNC_OBSTACK], +[AC_CACHE_CHECK([for obstacks], am_cv_func_obstack, + [AC_TRY_LINK([#include "obstack.h"], + [struct obstack *mem;obstack_free(mem,(char *) 0)], + am_cv_func_obstack=yes, + am_cv_func_obstack=no)]) + if test $am_cv_func_obstack = yes; then + AC_DEFINE(HAVE_OBSTACK) + else + LIBOBJS="$LIBOBJS obstack.o" + fi +]) + +dnl From Jim Meyering. Use this if you use the GNU error.[ch]. +dnl FIXME: Migrate into libit + +AC_DEFUN([AM_FUNC_ERROR_AT_LINE], +[AC_CACHE_CHECK([for error_at_line], am_cv_lib_error_at_line, + [AC_TRY_LINK([],[error_at_line(0, 0, "", 0, "");], + am_cv_lib_error_at_line=yes, + am_cv_lib_error_at_line=no)]) + if test $am_cv_lib_error_at_line = no; then + LIBOBJS="$LIBOBJS error.o" + fi + AC_SUBST(LIBOBJS)dnl +]) + +# Macro to add for using GNU gettext. +# Ulrich Drepper , 1995. + +# serial 1 +# Stephan Kulow: I put a KDE in it to avoid name conflicts + +AC_DEFUN([AM_KDE_GNU_GETTEXT], + [AC_REQUIRE([AC_PROG_MAKE_SET])dnl + AC_REQUIRE([AC_PROG_RANLIB])dnl + AC_REQUIRE([AC_HEADER_STDC])dnl + AC_REQUIRE([AC_TYPE_OFF_T])dnl + AC_REQUIRE([AC_TYPE_SIZE_T])dnl + AC_REQUIRE([AC_FUNC_ALLOCA])dnl + AC_REQUIRE([AC_FUNC_MMAP])dnl + AC_REQUIRE([AM_KDE_WITH_NLS])dnl + AC_CHECK_HEADERS([limits.h locale.h nl_types.h string.h values.h alloca.h]) + AC_CHECK_FUNCS([getcwd munmap putenv setlocale strchr strcasecmp \ +__argz_count __argz_stringify __argz_next]) + + AC_MSG_CHECKING(for stpcpy) + AC_CACHE_VAL(kde_cv_func_stpcpy, + [ + kde_safe_cxxflags=$CXXFLAGS + CXXFLAGS="-Werror" + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + AC_TRY_COMPILE([ + #include + ], + [ + char buffer[200]; + stpcpy(buffer, buffer); + ], + kde_cv_func_stpcpy=yes, + kde_cv_func_stpcpy=no) + AC_LANG_RESTORE + CXXFLAGS=$kde_safe_cxxflags + ]) + AC_MSG_RESULT($kde_cv_func_stpcpy) + if eval "test \"`echo $kde_cv_func_stpcpy`\" = yes"; then + AC_DEFINE(HAVE_STPCPY, 1, [Define if you have stpcpy]) + fi + + AM_LC_MESSAGES + + if test "x$CATOBJEXT" != "x"; then + if test "x$ALL_LINGUAS" = "x"; then + LINGUAS= + else + AC_MSG_CHECKING(for catalogs to be installed) + NEW_LINGUAS= + for lang in ${LINGUAS=$ALL_LINGUAS}; do + case "$ALL_LINGUAS" in + *$lang*) NEW_LINGUAS="$NEW_LINGUAS $lang" ;; + esac + done + LINGUAS=$NEW_LINGUAS + AC_MSG_RESULT($LINGUAS) + fi + + dnl Construct list of names of catalog files to be constructed. + if test -n "$LINGUAS"; then + for lang in $LINGUAS; do CATALOGS="$CATALOGS $lang$CATOBJEXT"; done + fi + fi + + ]) + +AC_DEFUN([AC_HAVE_XPM], + [AC_REQUIRE_CPP()dnl + AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) + + test -z "$XPM_LDFLAGS" && XPM_LDFLAGS= + test -z "$XPM_INCLUDE" && XPM_INCLUDE= + + AC_ARG_WITH(xpm,AC_HELP_STRING([--without-xpm],[disable color pixmap XPM tests]), + xpm_test=$withval, xpm_test="yes") + if test "x$xpm_test" = xno; then + ac_cv_have_xpm=no + else + AC_MSG_CHECKING(for XPM) + AC_CACHE_VAL(ac_cv_have_xpm, + [ + ac_save_ldflags="$LDFLAGS" + ac_save_cflags="$CFLAGS" + if test "x$kde_use_qt_emb" != "xyes" && test "x$kde_use_qt_mac" != "xyes"; then + LDFLAGS="$LDFLAGS $X_LDFLAGS $USER_LDFLAGS $LDFLAGS $XPM_LDFLAGS $all_libraries -lXpm -lX11 -lXext $LIBZ $LIBSOCKET" + else + LDFLAGS="$LDFLAGS $X_LDFLAGS $USER_LDFLAGS $LDFLAGS $XPM_LDFLAGS $all_libraries -lXpm $LIBZ $LIBSOCKET" + fi + CFLAGS="$CFLAGS $X_INCLUDES $USER_INCLUDES" + test -n "$XPM_INCLUDE" && CFLAGS="-I$XPM_INCLUDE $CFLAGS" + AC_TRY_LINK([#include ],[], + ac_cv_have_xpm="yes",ac_cv_have_xpm="no") + LDFLAGS="$ac_save_ldflags" + CFLAGS="$ac_save_cflags" + ])dnl + + if test "$ac_cv_have_xpm" = no; then + AC_MSG_RESULT(no) + XPM_LDFLAGS="" + XPMINC="" + $2 + else + AC_DEFINE(HAVE_XPM, 1, [Define if you have XPM support]) + if test "$XPM_LDFLAGS" = ""; then + XPMLIB='-lXpm $(LIB_X11)' + else + XPMLIB="-L$XPM_LDFLAGS -lXpm "'$(LIB_X11)' + fi + if test "$XPM_INCLUDE" = ""; then + XPMINC="" + else + XPMINC="-I$XPM_INCLUDE" + fi + AC_MSG_RESULT(yes) + $1 + fi + fi + AC_SUBST(XPMINC) + AC_SUBST(XPMLIB) +]) + +AC_DEFUN([AC_HAVE_DPMS], + [AC_REQUIRE_CPP()dnl + AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) + + test -z "$DPMS_LDFLAGS" && DPMS_LDFLAGS= + test -z "$DPMS_INCLUDE" && DPMS_INCLUDE= + DPMS_LIB= + + AC_ARG_WITH(dpms,AC_HELP_STRING([--without-dpms],[disable DPMS power saving]), + dpms_test=$withval, dpms_test="yes") + if test "x$dpms_test" = xno; then + ac_cv_have_dpms=no + else + AC_MSG_CHECKING(for DPMS) + dnl Note: ac_cv_have_dpms can be no, yes, or -lXdpms. + dnl 'yes' means DPMS_LIB="", '-lXdpms' means DPMS_LIB="-lXdpms". + AC_CACHE_VAL(ac_cv_have_dpms, + [ + if test "x$kde_use_qt_emb" = "xyes" || test "x$kde_use_qt_mac" = "xyes"; then + AC_MSG_RESULT(no) + ac_cv_have_dpms="no" + else + ac_save_ldflags="$LDFLAGS" + ac_save_cflags="$CFLAGS" + ac_save_libs="$LIBS" + LDFLAGS="$LDFLAGS $DPMS_LDFLAGS $all_libraries -lX11 -lXext $LIBSOCKET" + CFLAGS="$CFLAGS $X_INCLUDES" + test -n "$DPMS_INCLUDE" && CFLAGS="-I$DPMS_INCLUDE $CFLAGS" + AC_TRY_LINK([ + #include + #include + #include + #include + int foo_test_dpms() + { return DPMSSetTimeouts( 0, 0, 0, 0 ); }],[], + ac_cv_have_dpms="yes", [ + LDFLAGS="$ac_save_ldflags" + CFLAGS="$ac_save_cflags" + LDFLAGS="$LDFLAGS $DPMS_LDFLAGS $all_libraries -lX11 -lXext $LIBSOCKET" + LIBS="$LIBS -lXdpms" + CFLAGS="$CFLAGS $X_INCLUDES" + test -n "$DPMS_INCLUDE" && CFLAGS="-I$DPMS_INCLUDE $CFLAGS" + AC_TRY_LINK([ + #include + #include + #include + #include + int foo_test_dpms() + { return DPMSSetTimeouts( 0, 0, 0, 0 ); }],[], + [ + ac_cv_have_dpms="-lXdpms" + ],ac_cv_have_dpms="no") + ]) + LDFLAGS="$ac_save_ldflags" + CFLAGS="$ac_save_cflags" + LIBS="$ac_save_libs" + fi + ])dnl + + if test "$ac_cv_have_dpms" = no; then + AC_MSG_RESULT(no) + DPMS_LDFLAGS="" + DPMSINC="" + $2 + else + AC_DEFINE(HAVE_DPMS, 1, [Define if you have DPMS support]) + if test "$ac_cv_have_dpms" = "-lXdpms"; then + DPMS_LIB="-lXdpms" + fi + if test "$DPMS_LDFLAGS" = ""; then + DPMSLIB="$DPMS_LIB "'$(LIB_X11)' + else + DPMSLIB="$DPMS_LDFLAGS $DPMS_LIB "'$(LIB_X11)' + fi + if test "$DPMS_INCLUDE" = ""; then + DPMSINC="" + else + DPMSINC="-I$DPMS_INCLUDE" + fi + AC_MSG_RESULT(yes) + $1 + fi + fi + ac_save_cflags="$CFLAGS" + CFLAGS="$CFLAGS $X_INCLUDES" + test -n "$DPMS_INCLUDE" && CFLAGS="-I$DPMS_INCLUDE $CFLAGS" + AH_TEMPLATE(HAVE_DPMSCAPABLE_PROTO, + [Define if you have the DPMSCapable prototype in ]) + AC_CHECK_DECL(DPMSCapable, + AC_DEFINE(HAVE_DPMSCAPABLE_PROTO),, + [#include ]) + AH_TEMPLATE(HAVE_DPMSINFO_PROTO, + [Define if you have the DPMSInfo prototype in ]) + AC_CHECK_DECL(DPMSInfo, + AC_DEFINE(HAVE_DPMSINFO_PROTO),, + [#include ]) + CFLAGS="$ac_save_cflags" + AC_SUBST(DPMSINC) + AC_SUBST(DPMSLIB) +]) + +AC_DEFUN([AC_HAVE_GL], + [AC_REQUIRE_CPP()dnl + AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) + + test -z "$GL_LDFLAGS" && GL_LDFLAGS= + test -z "$GL_INCLUDE" && GL_INCLUDE= + + AC_ARG_WITH(gl,AC_HELP_STRING([--without-gl],[disable 3D GL modes]), + gl_test=$withval, gl_test="yes") + if test "x$kde_use_qt_emb" = "xyes"; then + # GL and Qt Embedded is a no-go for now. + ac_cv_have_gl=no + elif test "x$gl_test" = xno; then + ac_cv_have_gl=no + else + AC_MSG_CHECKING(for GL) + AC_CACHE_VAL(ac_cv_have_gl, + [ + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + ac_save_ldflags="$LDFLAGS" + ac_save_cxxflags="$CXXFLAGS" + LDFLAGS="$LDFLAGS $GL_LDFLAGS $X_LDFLAGS $all_libraries -lGL -lGLU" + test "x$kde_use_qt_mac" != xyes && test "x$kde_use_qt_emb" != xyes && LDFLAGS="$LDFLAGS -lX11" + LDFLAGS="$LDFLAGS $LIB_XEXT -lm $LIBSOCKET" + CXXFLAGS="$CFLAGS $X_INCLUDES" + test -n "$GL_INCLUDE" && CFLAGS="-I$GL_INCLUDE $CFLAGS" + AC_TRY_LINK([#include +#include +], [], + ac_cv_have_gl="yes", ac_cv_have_gl="no") + AC_LANG_RESTORE + LDFLAGS="$ac_save_ldflags" + CXXFLAGS="$ac_save_cxxflags" + ])dnl + + if test "$ac_cv_have_gl" = "no"; then + AC_MSG_RESULT(no) + GL_LDFLAGS="" + GLINC="" + $2 + else + AC_DEFINE(HAVE_GL, 1, [Defines if you have GL (Mesa, OpenGL, ...)]) + if test "$GL_LDFLAGS" = ""; then + GLLIB='-lGLU -lGL $(LIB_X11)' + else + GLLIB="$GL_LDFLAGS -lGLU -lGL "'$(LIB_X11)' + fi + if test "$GL_INCLUDE" = ""; then + GLINC="" + else + GLINC="-I$GL_INCLUDE" + fi + AC_MSG_RESULT($ac_cv_have_gl) + $1 + fi + fi + AC_SUBST(GLINC) + AC_SUBST(GLLIB) +]) + + + dnl shadow password and PAM magic - maintained by ossi@kde.org + +AC_DEFUN([KDE_PAM], [ + AC_REQUIRE([KDE_CHECK_LIBDL]) + + want_pam= + AC_ARG_WITH(pam, + AC_HELP_STRING([--with-pam[=ARG]],[enable support for PAM: ARG=[yes|no|service name]]), + [ if test "x$withval" = "xyes"; then + want_pam=yes + pam_service=kde + elif test "x$withval" = "xno"; then + want_pam=no + else + want_pam=yes + pam_service=$withval + fi + ], [ pam_service=kde ]) + + use_pam= + PAMLIBS= + if test "x$want_pam" != xno; then + AC_CHECK_LIB(pam, pam_start, [ + AC_CHECK_HEADER(security/pam_appl.h, + [ pam_header=security/pam_appl.h ], + [ AC_CHECK_HEADER(pam/pam_appl.h, + [ pam_header=pam/pam_appl.h ], + [ + AC_MSG_WARN([PAM detected, but no headers found! +Make sure you have the necessary development packages installed.]) + ] + ) + ] + ) + ], , $LIBDL) + if test -z "$pam_header"; then + if test "x$want_pam" = xyes; then + AC_MSG_ERROR([--with-pam was specified, but cannot compile with PAM!]) + fi + else + AC_DEFINE(HAVE_PAM, 1, [Defines if you have PAM (Pluggable Authentication Modules)]) + PAMLIBS="$PAM_MISC_LIB -lpam $LIBDL" + use_pam=yes + + dnl darwin claims to be something special + if test "$pam_header" = "pam/pam_appl.h"; then + AC_DEFINE(HAVE_PAM_PAM_APPL_H, 1, [Define if your PAM headers are in pam/ instead of security/]) + fi + + dnl test whether struct pam_message is const (Linux) or not (Sun) + AC_MSG_CHECKING(for const pam_message) + AC_EGREP_HEADER([struct pam_message], $pam_header, + [ AC_EGREP_HEADER([const struct pam_message], $pam_header, + [AC_MSG_RESULT([const: Linux-type PAM])], + [AC_MSG_RESULT([nonconst: Sun-type PAM]) + AC_DEFINE(PAM_MESSAGE_NONCONST, 1, [Define if your PAM support takes non-const arguments (Solaris)])] + )], + [AC_MSG_RESULT([not found - assume const, Linux-type PAM])]) + fi + fi + + AC_SUBST(PAMLIBS) +]) + +dnl DEF_PAM_SERVICE(arg name, full name, define name) +AC_DEFUN([DEF_PAM_SERVICE], [ + AC_ARG_WITH($1-pam, + AC_HELP_STRING([--with-$1-pam=[val]],[override PAM service from --with-pam for $2]), + [ if test "x$use_pam" = xyes; then + $3_PAM_SERVICE=$withval + else + AC_MSG_ERROR([Cannot use use --with-$1-pam, as no PAM was detected. +You may want to enforce it by using --with-pam.]) + fi + ], + [ if test "x$use_pam" = xyes; then + $3_PAM_SERVICE="$pam_service" + fi + ]) + if test -n "$$3_PAM_SERVICE"; then + AC_MSG_RESULT([The PAM service used by $2 will be $$3_PAM_SERVICE]) + AC_DEFINE_UNQUOTED($3_PAM_SERVICE, "$$3_PAM_SERVICE", [The PAM service to be used by $2]) + fi + AC_SUBST($3_PAM_SERVICE) +]) + +AC_DEFUN([KDE_SHADOWPASSWD], [ + AC_REQUIRE([KDE_PAM]) + + AC_CHECK_LIB(shadow, getspent, + [ LIBSHADOW="-lshadow" + ac_use_shadow=yes + ], + [ dnl for UnixWare + AC_CHECK_LIB(gen, getspent, + [ LIBGEN="-lgen" + ac_use_shadow=yes + ], + [ AC_CHECK_FUNC(getspent, + [ ac_use_shadow=yes ], + [ ac_use_shadow=no ]) + ]) + ]) + AC_SUBST(LIBSHADOW) + AC_SUBST(LIBGEN) + + AC_MSG_CHECKING([for shadow passwords]) + + AC_ARG_WITH(shadow, + AC_HELP_STRING([--with-shadow],[If you want shadow password support]), + [ if test "x$withval" != "xno"; then + use_shadow=yes + else + use_shadow=no + fi + ], [ + use_shadow="$ac_use_shadow" + ]) + + if test "x$use_shadow" = xyes; then + AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_SHADOW, 1, [Define if you use shadow passwords]) + else + AC_MSG_RESULT(no) + LIBSHADOW= + LIBGEN= + fi + + dnl finally make the relevant binaries setuid root, if we have shadow passwds. + dnl this still applies, if we could use it indirectly through pam. + if test "x$use_shadow" = xyes || + ( test "x$use_pam" = xyes && test "x$ac_use_shadow" = xyes ); then + case $host in + *-*-freebsd* | *-*-netbsd* | *-*-openbsd*) + SETUIDFLAGS="-m 4755 -o root";; + *) + SETUIDFLAGS="-m 4755";; + esac + fi + AC_SUBST(SETUIDFLAGS) + +]) + +AC_DEFUN([KDE_PASSWDLIBS], [ + AC_REQUIRE([KDE_MISC_TESTS]) dnl for LIBCRYPT + AC_REQUIRE([KDE_PAM]) + AC_REQUIRE([KDE_SHADOWPASSWD]) + + if test "x$use_pam" = "xyes"; then + PASSWDLIBS="$PAMLIBS" + else + PASSWDLIBS="$LIBCRYPT $LIBSHADOW $LIBGEN" + fi + + dnl FreeBSD uses a shadow-like setup, where /etc/passwd holds the users, but + dnl /etc/master.passwd holds the actual passwords. /etc/master.passwd requires + dnl root to read, so kcheckpass needs to be root (even when using pam, since pam + dnl may need to read /etc/master.passwd). + case $host in + *-*-freebsd*) + SETUIDFLAGS="-m 4755 -o root" + ;; + *) + ;; + esac + + AC_SUBST(PASSWDLIBS) +]) + +AC_DEFUN([KDE_CHECK_LIBDL], +[ +AC_CHECK_LIB(dl, dlopen, [ +LIBDL="-ldl" +ac_cv_have_dlfcn=yes +]) + +AC_CHECK_LIB(dld, shl_unload, [ +LIBDL="-ldld" +ac_cv_have_shload=yes +]) + +AC_SUBST(LIBDL) +]) + +AC_DEFUN([KDE_CHECK_DLOPEN], +[ +KDE_CHECK_LIBDL +AC_CHECK_HEADERS(dlfcn.h dl.h) +if test "$ac_cv_header_dlfcn_h" = "no"; then + ac_cv_have_dlfcn=no +fi + +if test "$ac_cv_header_dl_h" = "no"; then + ac_cv_have_shload=no +fi + +dnl XXX why change enable_dlopen? its already set by autoconf's AC_ARG_ENABLE +dnl (MM) +AC_ARG_ENABLE(dlopen, +AC_HELP_STRING([--disable-dlopen],[link statically [default=no]]), +enable_dlopen=$enableval, +enable_dlopen=yes) + +# override the user's opinion, if we know it better ;) +if test "$ac_cv_have_dlfcn" = "no" && test "$ac_cv_have_shload" = "no"; then + enable_dlopen=no +fi + +if test "$ac_cv_have_dlfcn" = "yes"; then + AC_DEFINE_UNQUOTED(HAVE_DLFCN, 1, [Define if you have dlfcn]) +fi + +if test "$ac_cv_have_shload" = "yes"; then + AC_DEFINE_UNQUOTED(HAVE_SHLOAD, 1, [Define if you have shload]) +fi + +if test "$enable_dlopen" = no ; then + test -n "$1" && eval $1 +else + test -n "$2" && eval $2 +fi + +]) + +AC_DEFUN([KDE_CHECK_DYNAMIC_LOADING], +[ +KDE_CHECK_DLOPEN(libtool_enable_shared=yes, libtool_enable_static=no) +KDE_PROG_LIBTOOL +AC_MSG_CHECKING([dynamic loading]) +eval "`egrep '^build_libtool_libs=' libtool`" +if test "$build_libtool_libs" = "yes" && test "$enable_dlopen" = "yes"; then + dynamic_loading=yes + AC_DEFINE_UNQUOTED(HAVE_DYNAMIC_LOADING) +else + dynamic_loading=no +fi +AC_MSG_RESULT($dynamic_loading) +if test "$dynamic_loading" = "yes"; then + $1 +else + $2 +fi +]) + +AC_DEFUN([KDE_ADD_INCLUDES], +[ +if test -z "$1"; then + test_include="Pix.h" +else + test_include="$1" +fi + +AC_MSG_CHECKING([for libg++ ($test_include)]) + +AC_CACHE_VAL(kde_cv_libgpp_includes, +[ +kde_cv_libgpp_includes=no + + for ac_dir in \ + \ + /usr/include/g++ \ + /usr/include \ + /usr/unsupported/include \ + /opt/include \ + $extra_include \ + ; \ + do + if test -r "$ac_dir/$test_include"; then + kde_cv_libgpp_includes=$ac_dir + break + fi + done +]) + +AC_MSG_RESULT($kde_cv_libgpp_includes) +if test "$kde_cv_libgpp_includes" != "no"; then + all_includes="-I$kde_cv_libgpp_includes $all_includes $USER_INCLUDES" +fi +]) +]) + +AC_DEFUN([KDE_CHECK_LIBPTHREAD], +[ + dnl This code is here specifically to handle the + dnl various flavors of threading library on FreeBSD + dnl 4-, 5-, and 6-, and the (weird) rules around it. + dnl There may be an environment PTHREAD_LIBS that + dnl specifies what to use; otherwise, search for it. + dnl -pthread is special cased and unsets LIBPTHREAD + dnl below if found. + LIBPTHREAD="" + + if test -n "$PTHREAD_LIBS"; then + if test "x$PTHREAD_LIBS" = "x-pthread" ; then + LIBPTHREAD="PTHREAD" + else + PTHREAD_LIBS_save="$PTHREAD_LIBS" + PTHREAD_LIBS=`echo "$PTHREAD_LIBS_save" | sed -e 's,^-l,,g'` + AC_MSG_CHECKING([for pthread_create in $PTHREAD_LIBS]) + KDE_CHECK_LIB($PTHREAD_LIBS, pthread_create, [ + LIBPTHREAD="$PTHREAD_LIBS_save"]) + PTHREAD_LIBS="$PTHREAD_LIBS_save" + fi + fi + + dnl Is this test really needed, in the face of the Tru64 test below? + if test -z "$LIBPTHREAD"; then + AC_CHECK_LIB(pthread, pthread_create, [LIBPTHREAD="-lpthread"]) + fi + + dnl This is a special Tru64 check, see BR 76171 issue #18. + if test -z "$LIBPTHREAD" ; then + AC_MSG_CHECKING([for pthread_create in -lpthread]) + kde_safe_libs=$LIBS + LIBS="$LIBS -lpthread" + AC_TRY_LINK([#include ],[(void)pthread_create(0,0,0,0);],[ + AC_MSG_RESULT(yes) + LIBPTHREAD="-lpthread"],[ + AC_MSG_RESULT(no)]) + LIBS=$kde_safe_libs + fi + + dnl Un-special-case for FreeBSD. + if test "x$LIBPTHREAD" = "xPTHREAD" ; then + LIBPTHREAD="" + fi + + AC_SUBST(LIBPTHREAD) +]) + +AC_DEFUN([KDE_CHECK_PTHREAD_OPTION], +[ + USE_THREADS="" + if test -z "$LIBPTHREAD"; then + KDE_CHECK_COMPILER_FLAG(pthread, [USE_THREADS="-D_THREAD_SAFE -pthread"]) + fi + + AH_VERBATIM(__svr_define, [ +#if defined(__SVR4) && !defined(__svr4__) +#define __svr4__ 1 +#endif +]) + case $host_os in + solaris*) + KDE_CHECK_COMPILER_FLAG(mt, [USE_THREADS="-mt"]) + CPPFLAGS="$CPPFLAGS -D_REENTRANT -D_POSIX_PTHREAD_SEMANTICS -DUSE_SOLARIS -DSVR4" + ;; + freebsd*) + CPPFLAGS="$CPPFLAGS -D_THREAD_SAFE $PTHREAD_CFLAGS" + ;; + aix*) + CPPFLAGS="$CPPFLAGS -D_THREAD_SAFE" + LIBPTHREAD="$LIBPTHREAD -lc_r" + ;; + linux*) CPPFLAGS="$CPPFLAGS -D_REENTRANT" + if test "$CXX" = "KCC"; then + CXXFLAGS="$CXXFLAGS --thread_safe" + NOOPT_CXXFLAGS="$NOOPT_CXXFLAGS --thread_safe" + fi + ;; + *) + ;; + esac + AC_SUBST(USE_THREADS) + AC_SUBST(LIBPTHREAD) +]) + +AC_DEFUN([KDE_CHECK_THREADING], +[ + AC_REQUIRE([KDE_CHECK_LIBPTHREAD]) + AC_REQUIRE([KDE_CHECK_PTHREAD_OPTION]) + dnl default is yes if libpthread is found and no if no libpthread is available + if test -z "$LIBPTHREAD"; then + if test -z "$USE_THREADS"; then + kde_check_threading_default=no + else + kde_check_threading_default=yes + fi + else + kde_check_threading_default=yes + fi + AC_ARG_ENABLE(threading,AC_HELP_STRING([--disable-threading],[disables threading even if libpthread found]), + kde_use_threading=$enableval, kde_use_threading=$kde_check_threading_default) + if test "x$kde_use_threading" = "xyes"; then + AC_DEFINE(HAVE_LIBPTHREAD, 1, [Define if you have a working libpthread (will enable threaded code)]) + fi +]) + +AC_DEFUN([KDE_TRY_LINK_PYTHON], +[ +if test "$kde_python_link_found" = no; then + +if test "$1" = normal; then + AC_MSG_CHECKING(if a Python application links) +else + AC_MSG_CHECKING(if Python depends on $2) +fi + +AC_CACHE_VAL(kde_cv_try_link_python_$1, +[ +kde_save_cflags="$CFLAGS" +CFLAGS="$CFLAGS $PYTHONINC" +kde_save_libs="$LIBS" +LIBS="$LIBS $LIBPYTHON $2 $LIBDL $LIBSOCKET" +kde_save_ldflags="$LDFLAGS" +LDFLAGS="$LDFLAGS $PYTHONLIB" + +AC_TRY_LINK( +[ +#include +],[ + PySys_SetArgv(1, 0); +], + [kde_cv_try_link_python_$1=yes], + [kde_cv_try_link_python_$1=no] +) +CFLAGS="$kde_save_cflags" +LIBS="$kde_save_libs" +LDFLAGS="$kde_save_ldflags" +]) + +if test "$kde_cv_try_link_python_$1" = "yes"; then + AC_MSG_RESULT(yes) + kde_python_link_found=yes + if test ! "$1" = normal; then + LIBPYTHON="$LIBPYTHON $2" + fi + $3 +else + AC_MSG_RESULT(no) + $4 +fi + +fi + +]) + +AC_DEFUN([KDE_CHECK_PYTHON_DIR], +[ +AC_MSG_CHECKING([for Python directory]) + +AC_CACHE_VAL(kde_cv_pythondir, +[ + if test -z "$PYTHONDIR"; then + kde_cv_pythondir=/usr/local + else + kde_cv_pythondir="$PYTHONDIR" + fi +]) + +AC_ARG_WITH(pythondir, +AC_HELP_STRING([--with-pythondir=pythondir],[use python installed in pythondir]), +[ + ac_python_dir=$withval +], ac_python_dir=$kde_cv_pythondir +) + +AC_MSG_RESULT($ac_python_dir) +]) + +AC_DEFUN([KDE_CHECK_PYTHON_INTERN], +[ +AC_REQUIRE([KDE_CHECK_LIBDL]) +AC_REQUIRE([KDE_CHECK_LIBPTHREAD]) +AC_REQUIRE([KDE_CHECK_PYTHON_DIR]) + +if test -z "$1"; then + version="1.5" +else + version="$1" +fi + +AC_MSG_CHECKING([for Python$version]) + +python_incdirs="$ac_python_dir/include /usr/include /usr/local/include/ $kde_extra_includes" +AC_FIND_FILE(Python.h, $python_incdirs, python_incdir) +if test ! -r $python_incdir/Python.h; then + AC_FIND_FILE(python$version/Python.h, $python_incdirs, python_incdir) + python_incdir=$python_incdir/python$version + if test ! -r $python_incdir/Python.h; then + python_incdir=no + fi +fi + +PYTHONINC=-I$python_incdir + +python_libdirs="$ac_python_dir/lib$kdelibsuff /usr/lib$kdelibsuff /usr/local /usr/lib$kdelibsuff $kde_extra_libs" +AC_FIND_FILE(libpython$version.so, $python_libdirs, python_libdir) +if test ! -r $python_libdir/libpython$version.so; then + AC_FIND_FILE(libpython$version.a, $python_libdirs, python_libdir) + if test ! -r $python_libdir/libpython$version.a; then + AC_FIND_FILE(python$version/config/libpython$version.a, $python_libdirs, python_libdir) + python_libdir=$python_libdir/python$version/config + if test ! -r $python_libdir/libpython$version.a; then + python_libdir=no + fi + fi +fi + +PYTHONLIB=-L$python_libdir +kde_orig_LIBPYTHON=$LIBPYTHON +if test -z "$LIBPYTHON"; then + LIBPYTHON=-lpython$version +fi + +AC_FIND_FILE(python$version/copy.py, $python_libdirs, python_moddir) +python_moddir=$python_moddir/python$version +if test ! -r $python_moddir/copy.py; then + python_moddir=no +fi + +PYTHONMODDIR=$python_moddir + +AC_MSG_RESULT(header $python_incdir library $python_libdir modules $python_moddir) + +if test x$python_incdir = xno || test x$python_libdir = xno || test x$python_moddir = xno; then + LIBPYTHON=$kde_orig_LIBPYTHON + test "x$PYTHONLIB" = "x-Lno" && PYTHONLIB="" + test "x$PYTHONINC" = "x-Ino" && PYTHONINC="" + $2 +else + dnl Note: this test is very weak + kde_python_link_found=no + KDE_TRY_LINK_PYTHON(normal) + KDE_TRY_LINK_PYTHON(m, -lm) + KDE_TRY_LINK_PYTHON(pthread, $LIBPTHREAD) + KDE_TRY_LINK_PYTHON(tcl, -ltcl) + KDE_TRY_LINK_PYTHON(db2, -ldb2) + KDE_TRY_LINK_PYTHON(m_and_thread, [$LIBPTHREAD -lm]) + KDE_TRY_LINK_PYTHON(m_and_thread_and_util, [$LIBPTHREAD -lm -lutil]) + KDE_TRY_LINK_PYTHON(m_and_thread_and_db3, [$LIBPTHREAD -lm -ldb-3 -lutil]) + KDE_TRY_LINK_PYTHON(pthread_and_db3, [$LIBPTHREAD -ldb-3]) + KDE_TRY_LINK_PYTHON(m_and_thread_and_db, [$LIBPTHREAD -lm -ldb -ltermcap -lutil]) + KDE_TRY_LINK_PYTHON(pthread_and_dl, [$LIBPTHREAD $LIBDL -lutil -lreadline -lncurses -lm]) + KDE_TRY_LINK_PYTHON(pthread_and_panel_curses, [$LIBPTHREAD $LIBDL -lm -lpanel -lcurses]) + KDE_TRY_LINK_PYTHON(m_and_thread_and_db_special, [$LIBPTHREAD -lm -ldb -lutil], [], + [AC_MSG_WARN([it seems, Python depends on another library. + Please set LIBPYTHON to '-lpython$version -lotherlib' before calling configure to fix this + and contact the authors to let them know about this problem]) + ]) + + LIBPYTHON="$LIBPYTHON $LIBDL $LIBSOCKET" + AC_SUBST(PYTHONINC) + AC_SUBST(PYTHONLIB) + AC_SUBST(LIBPYTHON) + AC_SUBST(PYTHONMODDIR) + AC_DEFINE(HAVE_PYTHON, 1, [Define if you have the development files for python]) +fi + +]) + + +AC_DEFUN([KDE_CHECK_PYTHON], +[ + KDE_CHECK_PYTHON_INTERN("2.4", + [KDE_CHECK_PYTHON_INTERN("2.3", + [KDE_CHECK_PYTHON_INTERN("2.2", + [KDE_CHECK_PYTHON_INTERN("2.1", + [KDE_CHECK_PYTHON_INTERN("2.0", + [KDE_CHECK_PYTHON_INTERN($1, $2) ]) + ]) + ]) + ]) + ]) +]) + +AC_DEFUN([KDE_CHECK_STL], +[ + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="`echo $CXXFLAGS | sed s/-fno-exceptions//`" + + AC_MSG_CHECKING([if C++ programs can be compiled]) + AC_CACHE_VAL(kde_cv_stl_works, + [ + AC_TRY_COMPILE([ +#include +using namespace std; +],[ + string astring="Hallo Welt."; + astring.erase(0, 6); // now astring is "Welt" + return 0; +], kde_cv_stl_works=yes, + kde_cv_stl_works=no) +]) + + AC_MSG_RESULT($kde_cv_stl_works) + + if test "$kde_cv_stl_works" = "yes"; then + # back compatible + AC_DEFINE_UNQUOTED(HAVE_SGI_STL, 1, [Define if you have a STL implementation by SGI]) + else + AC_MSG_ERROR([Your Installation isn't able to compile simple C++ programs. +Check config.log for details - if you're using a Linux distribution you might miss +a package named similar to libstdc++-dev.]) + fi + + CXXFLAGS="$ac_save_CXXFLAGS" + AC_LANG_RESTORE +]) + +AC_DEFUN([AC_FIND_QIMGIO], + [AC_REQUIRE([AC_FIND_JPEG]) +AC_REQUIRE([KDE_CHECK_EXTRA_LIBS]) +AC_MSG_CHECKING([for qimgio]) +AC_CACHE_VAL(ac_cv_lib_qimgio, +[ +AC_LANG_SAVE +AC_LANG_CPLUSPLUS +ac_save_LIBS="$LIBS" +ac_save_CXXFLAGS="$CXXFLAGS" +LIBS="$all_libraries -lqimgio -lpng -lz $LIBJPEG $LIBQT" +CXXFLAGS="$CXXFLAGS -I$qt_incdir $all_includes" +AC_TRY_RUN(dnl +[ +#include +#include +int main() { + QString t = "hallo"; + t.fill('t'); + qInitImageIO(); +} +], + ac_cv_lib_qimgio=yes, + ac_cv_lib_qimgio=no, + ac_cv_lib_qimgio=no) +LIBS="$ac_save_LIBS" +CXXFLAGS="$ac_save_CXXFLAGS" +AC_LANG_RESTORE +])dnl +if eval "test \"`echo $ac_cv_lib_qimgio`\" = yes"; then + LIBQIMGIO="-lqimgio -lpng -lz $LIBJPEG" + AC_MSG_RESULT(yes) + AC_DEFINE_UNQUOTED(HAVE_QIMGIO, 1, [Define if you have the Qt extension qimgio available]) + AC_SUBST(LIBQIMGIO) +else + AC_MSG_RESULT(not found) +fi +]) + +AC_DEFUN([AM_DISABLE_LIBRARIES], +[ + AC_PROVIDE([AM_ENABLE_STATIC]) + AC_PROVIDE([AM_ENABLE_SHARED]) + enable_static=no + enable_shared=yes +]) + + +AC_DEFUN([AC_CHECK_UTMP_FILE], +[ + AC_MSG_CHECKING([for utmp file]) + + AC_CACHE_VAL(kde_cv_utmp_file, + [ + kde_cv_utmp_file=no + + for ac_file in \ + \ + /var/run/utmp \ + /var/adm/utmp \ + /etc/utmp \ + ; \ + do + if test -r "$ac_file"; then + kde_cv_utmp_file=$ac_file + break + fi + done + ]) + + if test "$kde_cv_utmp_file" != "no"; then + AC_DEFINE_UNQUOTED(UTMP, "$kde_cv_utmp_file", [Define the file for utmp entries]) + $1 + AC_MSG_RESULT($kde_cv_utmp_file) + else + $2 + AC_MSG_RESULT([non found]) + fi +]) + + +AC_DEFUN([KDE_CREATE_SUBDIRSLIST], +[ + +DO_NOT_COMPILE="$DO_NOT_COMPILE CVS debian bsd-port admin" +TOPSUBDIRS="" + +if test ! -s $srcdir/subdirs; then + dnl Note: Makefile.common creates subdirs, so this is just a fallback + files=`cd $srcdir && ls -1` + dirs=`for i in $files; do if test -d $i; then echo $i; fi; done` + for i in $dirs; do + echo $i >> $srcdir/subdirs + done +fi + +ac_topsubdirs= +if test -s $srcdir/inst-apps; then + ac_topsubdirs="`cat $srcdir/inst-apps`" +elif test -s $srcdir/subdirs; then + ac_topsubdirs="`cat $srcdir/subdirs`" +fi + +for i in $ac_topsubdirs; do + AC_MSG_CHECKING([if $i should be compiled]) + if test -d $srcdir/$i; then + install_it="yes" + for j in $DO_NOT_COMPILE; do + if test $i = $j; then + install_it="no" + fi + done + else + install_it="no" + fi + AC_MSG_RESULT($install_it) + vari=`echo $i | sed -e 's,[[-+.@]],_,g'` + if test $install_it = "yes"; then + TOPSUBDIRS="$TOPSUBDIRS $i" + eval "$vari""_SUBDIR_included=yes" + else + eval "$vari""_SUBDIR_included=no" + fi +done + +AC_SUBST(TOPSUBDIRS) +]) + +AC_DEFUN([KDE_CHECK_NAMESPACES], +[ +AC_MSG_CHECKING(whether C++ compiler supports namespaces) +AC_LANG_SAVE +AC_LANG_CPLUSPLUS +AC_TRY_COMPILE([ +], +[ +namespace Foo { + extern int i; + namespace Bar { + extern int i; + } +} + +int Foo::i = 0; +int Foo::Bar::i = 1; +],[ + AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_NAMESPACES) +], [ +AC_MSG_RESULT(no) +]) +AC_LANG_RESTORE +]) + +dnl ------------------------------------------------------------------------ +dnl Check for S_ISSOCK macro. Doesn't exist on Unix SCO. faure@kde.org +dnl ------------------------------------------------------------------------ +dnl +AC_DEFUN([AC_CHECK_S_ISSOCK], +[ +AC_MSG_CHECKING(for S_ISSOCK) +AC_CACHE_VAL(ac_cv_have_s_issock, +[ +AC_TRY_LINK( +[ +#include +], +[ +struct stat buff; +int b = S_ISSOCK( buff.st_mode ); +], +ac_cv_have_s_issock=yes, +ac_cv_have_s_issock=no) +]) +AC_MSG_RESULT($ac_cv_have_s_issock) +if test "$ac_cv_have_s_issock" = "yes"; then + AC_DEFINE_UNQUOTED(HAVE_S_ISSOCK, 1, [Define if sys/stat.h declares S_ISSOCK.]) +fi + +AH_VERBATIM(_ISSOCK, +[ +#ifndef HAVE_S_ISSOCK +#define HAVE_S_ISSOCK +#define S_ISSOCK(mode) (1==0) +#endif +]) + +]) + +dnl ------------------------------------------------------------------------ +dnl Check for MAXPATHLEN macro, defines KDEMAXPATHLEN. faure@kde.org +dnl ------------------------------------------------------------------------ +dnl +AC_DEFUN([AC_CHECK_KDEMAXPATHLEN], +[ +AC_MSG_CHECKING(for MAXPATHLEN) +AC_CACHE_VAL(ac_cv_maxpathlen, +[ +cat > conftest.$ac_ext < +#endif +#include +#include +#ifndef MAXPATHLEN +#define MAXPATHLEN 1024 +#endif + +KDE_HELLO MAXPATHLEN + +EOF + +ac_try="$ac_cpp conftest.$ac_ext 2>/dev/null | grep '^KDE_HELLO' >conftest.out" + +if AC_TRY_EVAL(ac_try) && test -s conftest.out; then + ac_cv_maxpathlen=`sed 's#KDE_HELLO ##' conftest.out` +else + ac_cv_maxpathlen=1024 +fi + +rm conftest.* + +]) +AC_MSG_RESULT($ac_cv_maxpathlen) +AC_DEFINE_UNQUOTED(KDEMAXPATHLEN,$ac_cv_maxpathlen, [Define a safe value for MAXPATHLEN] ) +]) + +AC_DEFUN([KDE_CHECK_HEADER], +[ + AC_LANG_SAVE + kde_safe_cppflags=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $all_includes" + AC_LANG_CPLUSPLUS + AC_CHECK_HEADER([$1], [$2], [$3], [$4]) + CPPFLAGS=$kde_safe_cppflags + AC_LANG_RESTORE +]) + +AC_DEFUN([KDE_CHECK_HEADERS], +[ + AH_CHECK_HEADERS([$1]) + AC_LANG_SAVE + kde_safe_cppflags=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $all_includes" + AC_LANG_CPLUSPLUS + AC_CHECK_HEADERS([$1], [$2], [$3], [$4]) + CPPFLAGS=$kde_safe_cppflags + AC_LANG_RESTORE +]) + +AC_DEFUN([KDE_FAST_CONFIGURE], +[ + dnl makes configure fast (needs perl) + AC_ARG_ENABLE(fast-perl, AC_HELP_STRING([--disable-fast-perl],[disable fast Makefile generation (needs perl)]), + with_fast_perl=$enableval, with_fast_perl=yes) +]) + +AC_DEFUN([KDE_CONF_FILES], +[ + val= + if test -f $srcdir/configure.files ; then + val=`sed -e 's%^%\$(top_srcdir)/%' $srcdir/configure.files` + fi + CONF_FILES= + if test -n "$val" ; then + for i in $val ; do + CONF_FILES="$CONF_FILES $i" + done + fi + AC_SUBST(CONF_FILES) +])dnl + +dnl This sets the prefix, for arts and kdelibs +dnl Do NOT use in any other module. +dnl It only looks at --prefix, KDEDIR and falls back to /usr/local/kde +AC_DEFUN([KDE_SET_PREFIX_CORE], +[ + unset CDPATH + dnl make $KDEDIR the default for the installation + AC_PREFIX_DEFAULT(${KDEDIR:-/usr/local/kde}) + + if test "x$prefix" = "xNONE"; then + prefix=$ac_default_prefix + ac_configure_args="$ac_configure_args --prefix=$prefix" + fi + # And delete superfluous '/' to make compares easier + prefix=`echo "$prefix" | sed 's,//*,/,g' | sed -e 's,/$,,'` + kde_libs_htmldir=$prefix/share/doc/HTML/ + exec_prefix=`echo "$exec_prefix" | sed 's,//*,/,g' | sed -e 's,/$,,'` + + kde_libs_prefix='$(prefix)' + kde_libs_htmldir='$(kde_htmldir)' + AC_SUBST(kde_libs_prefix) + AC_SUBST(kde_libs_htmldir) + KDE_FAST_CONFIGURE + KDE_CONF_FILES +]) + + +AC_DEFUN([KDE_SET_PREFIX], +[ + unset CDPATH + dnl We can't give real code to that macro, only a value. + dnl It only matters for --help, since we set the prefix in this function anyway. + AC_PREFIX_DEFAULT(${KDEDIR:-the kde prefix}) + + KDE_SET_DEFAULT_BINDIRS + if test "x$prefix" = "xNONE"; then + dnl no prefix given: look for kde-config in the PATH and deduce the prefix from it + KDE_FIND_PATH(kde-config, KDECONFIG, [$kde_default_bindirs], [KDE_MISSING_PROG_ERROR(kde-config)], [], prepend) + else + dnl prefix given: look for kde-config, preferrably in prefix, otherwise in PATH + kde_save_PATH="$PATH" + PATH="$exec_prefix/bin:$prefix/bin:$PATH" + KDE_FIND_PATH(kde-config, KDECONFIG, [$kde_default_bindirs], [KDE_MISSING_PROG_ERROR(kde-config)], [], prepend) + PATH="$kde_save_PATH" + fi + + kde_libs_prefix=`$KDECONFIG --prefix` + if test -z "$kde_libs_prefix" || test ! -x "$kde_libs_prefix"; then + AC_MSG_ERROR([$KDECONFIG --prefix outputed the non existant prefix '$kde_libs_prefix' for kdelibs. + This means it has been moved since you installed it. + This won't work. Please recompile kdelibs for the new prefix. + ]) + fi + kde_libs_htmldir=`$KDECONFIG --install html --expandvars` + kde_libs_suffix=`$KDECONFIG --libsuffix` + + AC_MSG_CHECKING([where to install]) + if test "x$prefix" = "xNONE"; then + prefix=$kde_libs_prefix + AC_MSG_RESULT([$prefix (as returned by kde-config)]) + else + dnl --prefix was given. Compare prefixes and warn (in configure.in.bot.end) if different + given_prefix=$prefix + AC_MSG_RESULT([$prefix (as requested)]) + fi + + # And delete superfluous '/' to make compares easier + prefix=`echo "$prefix" | sed 's,//*,/,g' | sed -e 's,/$,,'` + exec_prefix=`echo "$exec_prefix" | sed 's,//*,/,g' | sed -e 's,/$,,'` + given_prefix=`echo "$given_prefix" | sed 's,//*,/,g' | sed -e 's,/$,,'` + + AC_SUBST(KDECONFIG) + AC_SUBST(kde_libs_prefix) + AC_SUBST(kde_libs_htmldir) + + KDE_FAST_CONFIGURE + KDE_CONF_FILES +]) + +pushdef([AC_PROG_INSTALL], +[ + dnl our own version, testing for a -p flag + popdef([AC_PROG_INSTALL]) + dnl as AC_PROG_INSTALL works as it works we first have + dnl to save if the user didn't specify INSTALL, as the + dnl autoconf one overwrites INSTALL and we have no chance to find + dnl out afterwards + test -n "$INSTALL" && kde_save_INSTALL_given=$INSTALL + test -n "$INSTALL_PROGRAM" && kde_save_INSTALL_PROGRAM_given=$INSTALL_PROGRAM + test -n "$INSTALL_SCRIPT" && kde_save_INSTALL_SCRIPT_given=$INSTALL_SCRIPT + AC_PROG_INSTALL + + if test -z "$kde_save_INSTALL_given" ; then + # OK, user hasn't given any INSTALL, autoconf found one for us + # now we test, if it supports the -p flag + AC_MSG_CHECKING(for -p flag to install) + rm -f confinst.$$.* > /dev/null 2>&1 + echo "Testtest" > confinst.$$.orig + ac_res=no + if ${INSTALL} -p confinst.$$.orig confinst.$$.new > /dev/null 2>&1 ; then + if test -f confinst.$$.new ; then + # OK, -p seems to do no harm to install + INSTALL="${INSTALL} -p" + ac_res=yes + fi + fi + rm -f confinst.$$.* + AC_MSG_RESULT($ac_res) + fi + dnl the following tries to resolve some signs and wonders coming up + dnl with different autoconf/automake versions + dnl e.g.: + dnl *automake 1.4 install-strip sets A_M_INSTALL_PROGRAM_FLAGS to -s + dnl and has INSTALL_PROGRAM = @INSTALL_PROGRAM@ $(A_M_INSTALL_PROGRAM_FLAGS) + dnl it header-vars.am, so there the actual INSTALL_PROGRAM gets the -s + dnl *automake 1.4a (and above) use INSTALL_STRIP_FLAG and only has + dnl INSTALL_PROGRAM = @INSTALL_PROGRAM@ there, but changes the + dnl install-@DIR@PROGRAMS targets to explicitly use that flag + dnl *autoconf 2.13 is dumb, and thinks it can use INSTALL_PROGRAM as + dnl INSTALL_SCRIPT, which breaks with automake <= 1.4 + dnl *autoconf >2.13 (since 10.Apr 1999) has not that failure + dnl *sometimes KDE does not use the install-@DIR@PROGRAM targets from + dnl automake (due to broken Makefile.am or whatever) to install programs, + dnl and so does not see the -s flag in automake > 1.4 + dnl to clean up that mess we: + dnl +set INSTALL_PROGRAM to use INSTALL_STRIP_FLAG + dnl which cleans KDE's program with automake > 1.4; + dnl +set INSTALL_SCRIPT to only use INSTALL, to clean up autoconf's problems + dnl with automake<=1.4 + dnl note that dues to this sometimes two '-s' flags are used (if KDE + dnl properly uses install-@DIR@PROGRAMS, but I don't care + dnl + dnl And to all this comes, that I even can't write in comments variable + dnl names used by automake, because it is so stupid to think I wanted to + dnl _use_ them, therefor I have written A_M_... instead of AM_ + dnl hmm, I wanted to say something ... ahh yes: Arghhh. + + if test -z "$kde_save_INSTALL_PROGRAM_given" ; then + INSTALL_PROGRAM='${INSTALL} $(INSTALL_STRIP_FLAG)' + fi + if test -z "$kde_save_INSTALL_SCRIPT_given" ; then + INSTALL_SCRIPT='${INSTALL}' + fi +])dnl + +AC_DEFUN([KDE_LANG_CPLUSPLUS], +[AC_LANG_CPLUSPLUS +ac_link='rm -rf SunWS_cache; ${CXX-g++} -o conftest${ac_exeext} $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&AC_FD_CC' +pushdef([AC_LANG_CPLUSPLUS], [popdef([AC_LANG_CPLUSPLUS]) KDE_LANG_CPLUSPLUS]) +]) + +pushdef([AC_LANG_CPLUSPLUS], +[popdef([AC_LANG_CPLUSPLUS]) +KDE_LANG_CPLUSPLUS +]) + +AC_DEFUN([KDE_CHECK_LONG_LONG], +[ +AC_MSG_CHECKING(for long long) +AC_CACHE_VAL(kde_cv_c_long_long, +[ + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + AC_TRY_LINK([], [ + long long foo = 0; + foo = foo+1; + ], + kde_cv_c_long_long=yes, kde_cv_c_long_long=no) + AC_LANG_RESTORE +]) +AC_MSG_RESULT($kde_cv_c_long_long) +if test "$kde_cv_c_long_long" = yes; then + AC_DEFINE(HAVE_LONG_LONG, 1, [Define if you have long long as datatype]) +fi +]) + +AC_DEFUN([KDE_CHECK_LIB], +[ + kde_save_LDFLAGS="$LDFLAGS" + dnl AC_CHECK_LIB modifies LIBS, so save it here + kde_save_LIBS="$LIBS" + LDFLAGS="$LDFLAGS $all_libraries" + case $host_os in + aix*) LDFLAGS="-brtl $LDFLAGS" + test "$GCC" = yes && LDFLAGS="-Wl,$LDFLAGS" + ;; + esac + AC_CHECK_LIB($1, $2, $3, $4, $5) + LDFLAGS="$kde_save_LDFLAGS" + LIBS="$kde_save_LIBS" +]) + +AC_DEFUN([KDE_JAVA_PREFIX], +[ + dir=`dirname "$1"` + base=`basename "$1"` + list=`ls -1 $dir 2> /dev/null` + for entry in $list; do + if test -d $dir/$entry/bin; then + case $entry in + $base) + javadirs="$javadirs $dir/$entry/bin" + ;; + esac + elif test -d $dir/$entry/jre/bin; then + case $entry in + $base) + javadirs="$javadirs $dir/$entry/jre/bin" + ;; + esac + fi + done +]) + +dnl KDE_CHEC_JAVA_DIR(onlyjre) +AC_DEFUN([KDE_CHECK_JAVA_DIR], +[ + +AC_ARG_WITH(java, +AC_HELP_STRING([--with-java=javadir],[use java installed in javadir, --without-java disables]), +[ ac_java_dir=$withval +], ac_java_dir="" +) + +AC_MSG_CHECKING([for Java]) + +dnl at this point ac_java_dir is either a dir, 'no' to disable, or '' to say look in $PATH +if test "x$ac_java_dir" = "xno"; then + kde_java_bindir=no + kde_java_includedir=no + kde_java_libjvmdir=no + kde_java_libgcjdir=no + kde_java_libhpidir=no +else + if test "x$ac_java_dir" = "x"; then + + + dnl No option set -> collect list of candidate paths + if test -n "$JAVA_HOME"; then + KDE_JAVA_PREFIX($JAVA_HOME) + fi + KDE_JAVA_PREFIX(/usr/j2se) + KDE_JAVA_PREFIX(/usr/lib/j2se) + KDE_JAVA_PREFIX(/usr/j*dk*) + KDE_JAVA_PREFIX(/usr/lib/j*dk*) + KDE_JAVA_PREFIX(/opt/j*sdk*) + KDE_JAVA_PREFIX(/usr/lib/java*) + KDE_JAVA_PREFIX(/usr/java*) + KDE_JAVA_PREFIX(/usr/java/j*dk*) + KDE_JAVA_PREFIX(/usr/java/j*re*) + KDE_JAVA_PREFIX(/usr/lib/SunJava2*) + KDE_JAVA_PREFIX(/usr/lib/SunJava*) + KDE_JAVA_PREFIX(/usr/lib/IBMJava2*) + KDE_JAVA_PREFIX(/usr/lib/IBMJava*) + KDE_JAVA_PREFIX(/opt/java*) + + kde_cv_path="NONE" + kde_save_IFS=$IFS + IFS=':' + for dir in $PATH; do + if test -d "$dir"; then + javadirs="$javadirs $dir" + fi + done + IFS=$kde_save_IFS + jredirs= + + dnl Now javadirs contains a list of paths that exist, all ending with bin/ + for dir in $javadirs; do + dnl Check for the java executable + if test -x "$dir/java"; then + dnl And also check for a libjvm.so somewhere under there + dnl Since we have to go to the parent dir, /usr/bin is excluded, /usr is too big. + if test "$dir" != "/usr/bin"; then + libjvmdir=`find $dir/.. -name libjvm.so | sed 's,libjvm.so,,'|head -n 1` + if test ! -f $libjvmdir/libjvm.so; then continue; fi + jredirs="$jredirs $dir" + fi + fi + done + + dnl Now jredirs contains a reduced list, of paths where both java and ../**/libjvm.so was found + JAVAC= + JAVA= + kde_java_bindir=no + for dir in $jredirs; do + JAVA="$dir/java" + kde_java_bindir=$dir + if test -x "$dir/javac"; then + JAVAC="$dir/javac" + break + fi + done + + if test -n "$JAVAC"; then + dnl this substitution might not work - well, we test for jni.h below + kde_java_includedir=`echo $JAVAC | sed -e 's,bin/javac$,include/,'` + else + kde_java_includedir=no + fi + else + dnl config option set + kde_java_bindir=$ac_java_dir/bin + if test -x $ac_java_dir/bin/java && test ! -x $ac_java_dir/bin/javac; then + kde_java_includedir=no + else + kde_java_includedir=$ac_java_dir/include + fi + fi +fi + +dnl At this point kde_java_bindir and kde_java_includedir are either set or "no" +if test "x$kde_java_bindir" != "xno"; then + + dnl Look for libjvm.so + kde_java_libjvmdir=`find $kde_java_bindir/.. -name libjvm.so | sed 's,libjvm.so,,'|head -n 1` + dnl Look for libgcj.so + kde_java_libgcjdir=`find $kde_java_bindir/.. -name libgcj.so | sed 's,libgcj.so,,'|head -n 1` + dnl Look for libhpi.so and avoid green threads + kde_java_libhpidir=`find $kde_java_bindir/.. -name libhpi.so | grep -v green | sed 's,libhpi.so,,' | head -n 1` + + dnl Now check everything's fine under there + dnl the include dir is our flag for having the JDK + if test -d "$kde_java_includedir"; then + if test ! -x "$kde_java_bindir/javac"; then + AC_MSG_ERROR([javac not found under $kde_java_bindir - it seems you passed a wrong --with-java.]) + fi + if test ! -x "$kde_java_bindir/javah"; then + AC_MSG_ERROR([javah not found under $kde_java_bindir. javac was found though! Use --with-java or --without-java.]) + fi + if test ! -x "$kde_java_bindir/jar"; then + AC_MSG_ERROR([jar not found under $kde_java_bindir. javac was found though! Use --with-java or --without-java.]) + fi + if test ! -r "$kde_java_includedir/jni.h"; then + AC_MSG_ERROR([jni.h not found under $kde_java_includedir. Use --with-java or --without-java.]) + fi + + jni_includes="-I$kde_java_includedir" + dnl Strange thing, jni.h requires jni_md.h which is under genunix here.. + dnl and under linux here.. + + dnl not needed for gcj + + if test "x$kde_java_libgcjdir" = "x"; then + test -d "$kde_java_includedir/linux" && jni_includes="$jni_includes -I$kde_java_includedir/linux" + test -d "$kde_java_includedir/solaris" && jni_includes="$jni_includes -I$kde_java_includedir/solaris" + test -d "$kde_java_includedir/genunix" && jni_includes="$jni_includes -I$kde_java_includedir/genunix" + fi + + else + JAVAC= + jni_includes= + fi + + if test "x$kde_java_libgcjdir" = "x"; then + if test ! -r "$kde_java_libjvmdir/libjvm.so"; then + AC_MSG_ERROR([libjvm.so not found under $kde_java_libjvmdir. Use --without-java.]) + fi + else + if test ! -r "$kde_java_libgcjdir/libgcj.so"; then + AC_MSG_ERROR([libgcj.so not found under $kde_java_libgcjdir. Use --without-java.]) + fi + fi + + if test ! -x "$kde_java_bindir/java"; then + AC_MSG_ERROR([java not found under $kde_java_bindir. javac was found though! Use --with-java or --without-java.]) + fi + + dnl not needed for gcj compile + + if test "x$kde_java_libgcjdir" = "x"; then + if test ! -r "$kde_java_libhpidir/libhpi.so"; then + AC_MSG_ERROR([libhpi.so not found under $kde_java_libhpidir. Use --without-java.]) + fi + fi + + if test -n "$jni_includes"; then + dnl Check for JNI version + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + ac_cxxflags_safe="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $all_includes $jni_includes" + + AC_TRY_COMPILE([ + #include + ], + [ + #ifndef JNI_VERSION_1_2 + Syntax Error + #endif + ],[ kde_jni_works=yes ], + [ kde_jni_works=no ]) + + if test $kde_jni_works = no; then + AC_MSG_ERROR([Incorrect version of $kde_java_includedir/jni.h. + You need to have Java Development Kit (JDK) version 1.2. + + Use --with-java to specify another location. + Use --without-java to configure without java support. + Or download a newer JDK and try again. + See e.g. http://java.sun.com/products/jdk/1.2 ]) + fi + + CXXFLAGS="$ac_cxxflags_safe" + AC_LANG_RESTORE + + dnl All tests ok, inform and subst the variables + + JAVAC=$kde_java_bindir/javac + JAVAH=$kde_java_bindir/javah + JAR=$kde_java_bindir/jar + AC_DEFINE_UNQUOTED(PATH_JAVA, "$kde_java_bindir/java", [Define where your java executable is]) + if test "x$kde_java_libgcjdir" = "x"; then + JVMLIBS="-L$kde_java_libjvmdir -ljvm -L$kde_java_libhpidir -lhpi" + else + JVMLIBS="-L$kde_java_libgcjdir -lgcj" + fi + AC_MSG_RESULT([java JDK in $kde_java_bindir]) + + else + AC_DEFINE_UNQUOTED(PATH_JAVA, "$kde_java_bindir/java", [Define where your java executable is]) + AC_MSG_RESULT([java JRE in $kde_java_bindir]) + fi +elif test -d "/Library/Java/Home"; then + kde_java_bindir="/Library/Java/Home/bin" + jni_includes="-I/Library/Java/Home/include" + + JAVAC=$kde_java_bindir/javac + JAVAH=$kde_java_bindir/javah + JAR=$kde_java_bindir/jar + JVMLIBS="-Xlinker -framework -Xlinker JavaVM" + + AC_DEFINE_UNQUOTED(PATH_JAVA, "$kde_java_bindir/java", [Define where your java executable is]) + AC_MSG_RESULT([Apple Java Framework]) +else + AC_MSG_RESULT([none found]) +fi + +AC_SUBST(JAVAC) +AC_SUBST(JAVAH) +AC_SUBST(JAR) +AC_SUBST(JVMLIBS) +AC_SUBST(jni_includes) + +# for backward compat +kde_cv_java_includedir=$kde_java_includedir +kde_cv_java_bindir=$kde_java_bindir +]) + +dnl this is a redefinition of autoconf 2.5x's AC_FOREACH. +dnl When the argument list becomes big, as in KDE for AC_OUTPUT in +dnl big packages, m4_foreach is dog-slow. So use our own version of +dnl it. (matz@kde.org) +m4_define([mm_foreach], +[m4_pushdef([$1])_mm_foreach($@)m4_popdef([$1])]) +m4_define([mm_car], [[$1]]) +m4_define([mm_car2], [[$@]]) +m4_define([_mm_foreach], +[m4_if(m4_quote($2), [], [], + [m4_define([$1], mm_car($2))$3[]_mm_foreach([$1], + mm_car2(m4_shift($2)), + [$3])])]) +m4_define([AC_FOREACH], +[mm_foreach([$1], m4_split(m4_normalize([$2])), [$3])]) + +AC_DEFUN([KDE_NEED_FLEX], +[ +kde_libs_safe=$LIBS +LIBS="$LIBS $USER_LDFLAGS" +AM_PROG_LEX +LIBS=$kde_libs_safe +if test -z "$LEXLIB"; then + AC_MSG_ERROR([You need to have flex installed.]) +fi +AC_SUBST(LEXLIB) +]) + +AC_DEFUN([AC_PATH_QTOPIA], +[ + dnl TODO: use AC_CACHE_VAL + + if test -z "$1"; then + qtopia_minver_maj=1 + qtopia_minver_min=5 + qtopia_minver_pat=0 + else + qtopia_minver_maj=`echo "$1" | sed -e "s/^\(.*\)\..*\..*$/\1/"` + qtopia_minver_min=`echo "$1" | sed -e "s/^.*\.\(.*\)\..*$/\1/"` + qtopia_minver_pat=`echo "$1" | sed -e "s/^.*\..*\.\(.*\)$/\1/"` + fi + + qtopia_minver="$qtopia_minver_maj$qtopia_minver_min$qtopia_minver_pat" + qtopia_minverstr="$qtopia_minver_maj.$qtopia_minver_min.$qtopia_minver_pat" + + AC_REQUIRE([AC_PATH_QT]) + + AC_MSG_CHECKING([for Qtopia]) + + LIB_QTOPIA="-lqpe" + AC_SUBST(LIB_QTOPIA) + + kde_qtopia_dirs="$QPEDIR /opt/Qtopia" + + ac_qtopia_incdir=NO + + AC_ARG_WITH(qtopia-dir, + AC_HELP_STRING([--with-qtopia-dir=DIR],[where the root of Qtopia is installed]), + [ ac_qtopia_incdir="$withval"/include] ) + + qtopia_incdirs="" + for dir in $kde_qtopia_dirs; do + qtopia_incdirs="$qtopia_incdirs $dir/include" + done + + if test ! "$ac_qtopia_incdir" = "NO"; then + qtopia_incdirs="$ac_qtopia_incdir $qtopia_incdirs" + fi + + qtopia_incdir="" + AC_FIND_FILE(qpe/qpeapplication.h, $qtopia_incdirs, qtopia_incdir) + ac_qtopia_incdir="$qtopia_incdir" + + if test -z "$qtopia_incdir"; then + AC_MSG_ERROR([Cannot find Qtopia headers. Please check your installation.]) + fi + + qtopia_ver_maj=`cat $qtopia_incdir/qpe/version.h | sed -n -e 's,.*QPE_VERSION "\(.*\)\..*\..*".*,\1,p'`; + qtopia_ver_min=`cat $qtopia_incdir/qpe/version.h | sed -n -e 's,.*QPE_VERSION ".*\.\(.*\)\..*".*,\1,p'`; + qtopia_ver_pat=`cat $qtopia_incdir/qpe/version.h | sed -n -e 's,.*QPE_VERSION ".*\..*\.\(.*\)".*,\1,p'`; + + qtopia_ver="$qtopia_ver_maj$qtopia_ver_min$qtopia_ver_pat" + qtopia_verstr="$qtopia_ver_maj.$qtopia_ver_min.$qtopia_ver_pat" + if test "$qtopia_ver" -lt "$qtopia_minver"; then + AC_MSG_ERROR([found Qtopia version $qtopia_verstr but version $qtopia_minverstr +is required.]) + fi + + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + + ac_cxxflags_safe="$CXXFLAGS" + ac_ldflags_safe="$LDFLAGS" + ac_libs_safe="$LIBS" + + CXXFLAGS="$CXXFLAGS -I$qtopia_incdir $all_includes" + LDFLAGS="$LDFLAGS $QT_LDFLAGS $all_libraries $USER_LDFLAGS $KDE_MT_LDFLAGS" + LIBS="$LIBS $LIB_QTOPIA $LIBQT" + + cat > conftest.$ac_ext < +#include + +int main( int argc, char **argv ) +{ + QPEApplication app( argc, argv ); + return 0; +} +EOF + + if AC_TRY_EVAL(ac_link) && test -s conftest; then + rm -f conftest* + else + rm -f conftest* + AC_MSG_ERROR([Cannot link small Qtopia Application. For more details look at +the end of config.log]) + fi + + CXXFLAGS="$ac_cxxflags_safe" + LDFLAGS="$ac_ldflags_safe" + LIBS="$ac_libs_safe" + + AC_LANG_RESTORE + + QTOPIA_INCLUDES="-I$qtopia_incdir" + AC_SUBST(QTOPIA_INCLUDES) + + AC_MSG_RESULT([found version $qtopia_verstr with headers at $qtopia_incdir]) +]) + + +AC_DEFUN([KDE_INIT_DOXYGEN], +[ +AC_MSG_CHECKING([for Qt docs]) +kde_qtdir= +if test "${with_qt_dir+set}" = set; then + kde_qtdir="$with_qt_dir" +fi + +AC_FIND_FILE(qsql.html, [ $kde_qtdir/doc/html $QTDIR/doc/html /usr/share/doc/packages/qt3/html /usr/lib/qt/doc /usr/lib/qt3/doc /usr/lib/qt3/doc/html /usr/doc/qt3/html /usr/doc/qt3 /usr/share/doc/qt3-doc /usr/share/qt3/doc/html /usr/X11R6/share/doc/qt/html ], QTDOCDIR) +AC_MSG_RESULT($QTDOCDIR) + +AC_SUBST(QTDOCDIR) + +KDE_FIND_PATH(dot, DOT, [], []) +if test -n "$DOT"; then + KDE_HAVE_DOT="YES" +else + KDE_HAVE_DOT="NO" +fi +AC_SUBST(KDE_HAVE_DOT) +KDE_FIND_PATH(doxygen, DOXYGEN, [], []) +AC_SUBST(DOXYGEN) + +DOXYGEN_PROJECT_NAME="$1" +DOXYGEN_PROJECT_NUMBER="$2" +AC_SUBST(DOXYGEN_PROJECT_NAME) +AC_SUBST(DOXYGEN_PROJECT_NUMBER) + +KDE_HAS_DOXYGEN=no +if test -n "$DOXYGEN" && test -x "$DOXYGEN" && test -f $QTDOCDIR/qsql.html; then + KDE_HAS_DOXYGEN=yes +fi +AC_SUBST(KDE_HAS_DOXYGEN) + +]) + + +AC_DEFUN([AC_FIND_BZIP2], +[ +AC_MSG_CHECKING([for bzDecompress in libbz2]) +AC_CACHE_VAL(ac_cv_lib_bzip2, +[ +AC_LANG_SAVE +AC_LANG_CPLUSPLUS +kde_save_LIBS="$LIBS" +LIBS="$all_libraries $USER_LDFLAGS -lbz2 $LIBSOCKET" +kde_save_CXXFLAGS="$CXXFLAGS" +CXXFLAGS="$CXXFLAGS $all_includes $USER_INCLUDES" +AC_TRY_LINK(dnl +[ +#define BZ_NO_STDIO +#include +], + [ bz_stream s; (void) bzDecompress(&s); ], + eval "ac_cv_lib_bzip2='-lbz2'", + eval "ac_cv_lib_bzip2=no") +LIBS="$kde_save_LIBS" +CXXFLAGS="$kde_save_CXXFLAGS" +AC_LANG_RESTORE +])dnl +AC_MSG_RESULT($ac_cv_lib_bzip2) + +if test ! "$ac_cv_lib_bzip2" = no; then + BZIP2DIR=bzip2 + + LIBBZ2="$ac_cv_lib_bzip2" + AC_SUBST(LIBBZ2) + +else + + cxx_shared_flag= + ld_shared_flag= + KDE_CHECK_COMPILER_FLAG(shared, [ + ld_shared_flag="-shared" + ]) + KDE_CHECK_COMPILER_FLAG(fPIC, [ + cxx_shared_flag="-fPIC" + ]) + + AC_MSG_CHECKING([for BZ2_bzDecompress in (shared) libbz2]) + AC_CACHE_VAL(ac_cv_lib_bzip2_prefix, + [ + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + kde_save_LIBS="$LIBS" + LIBS="$all_libraries $USER_LDFLAGS $ld_shared_flag -lbz2 $LIBSOCKET" + kde_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CFLAGS $cxx_shared_flag $all_includes $USER_INCLUDES" + + AC_TRY_LINK(dnl + [ + #define BZ_NO_STDIO + #include + ], + [ bz_stream s; (void) BZ2_bzDecompress(&s); ], + eval "ac_cv_lib_bzip2_prefix='-lbz2'", + eval "ac_cv_lib_bzip2_prefix=no") + LIBS="$kde_save_LIBS" + CXXFLAGS="$kde_save_CXXFLAGS" + AC_LANG_RESTORE + ])dnl + + AC_MSG_RESULT($ac_cv_lib_bzip2_prefix) + + if test ! "$ac_cv_lib_bzip2_prefix" = no; then + BZIP2DIR=bzip2 + + LIBBZ2="$ac_cv_lib_bzip2_prefix" + AC_SUBST(LIBBZ2) + + AC_DEFINE(NEED_BZ2_PREFIX, 1, [Define if the libbz2 functions need the BZ2_ prefix]) + dnl else, we just ignore this + fi + +fi +AM_CONDITIONAL(include_BZIP2, test -n "$BZIP2DIR") +]) + +dnl ------------------------------------------------------------------------ +dnl Try to find the SSL headers and libraries. +dnl $(SSL_LDFLAGS) will be -Lsslliblocation (if needed) +dnl and $(SSL_INCLUDES) will be -Isslhdrlocation (if needed) +dnl ------------------------------------------------------------------------ +dnl +AC_DEFUN([KDE_CHECK_SSL], +[ +LIBSSL="-lssl -lcrypto" +AC_REQUIRE([KDE_CHECK_LIB64]) + +ac_ssl_includes=NO ac_ssl_libraries=NO +ssl_libraries="" +ssl_includes="" +AC_ARG_WITH(ssl-dir, + AC_HELP_STRING([--with-ssl-dir=DIR],[where the root of OpenSSL is installed]), + [ ac_ssl_includes="$withval"/include + ac_ssl_libraries="$withval"/lib$kdelibsuff + ]) + +want_ssl=yes +AC_ARG_WITH(ssl, + AC_HELP_STRING([--without-ssl],[disable SSL checks]), + [want_ssl=$withval]) + +if test $want_ssl = yes; then + +AC_MSG_CHECKING(for OpenSSL) + +AC_CACHE_VAL(ac_cv_have_ssl, +[#try to guess OpenSSL locations + + ssl_incdirs="/usr/include /usr/local/include /usr/ssl/include /usr/local/ssl/include $prefix/include $kde_extra_includes" + ssl_incdirs="$ac_ssl_includes $ssl_incdirs" + AC_FIND_FILE(openssl/ssl.h, $ssl_incdirs, ssl_incdir) + ac_ssl_includes="$ssl_incdir" + + ssl_libdirs="/usr/lib$kdelibsuff /usr/local/lib$kdelibsuff /usr/ssl/lib$kdelibsuff /usr/local/ssl/lib$kdelibsuff $libdir $prefix/lib$kdelibsuff $exec_prefix/lib$kdelibsuff $kde_extra_libs" + if test ! "$ac_ssl_libraries" = "NO"; then + ssl_libdirs="$ac_ssl_libraries $ssl_libdirs" + fi + + test=NONE + ssl_libdir=NONE + for dir in $ssl_libdirs; do + try="ls -1 $dir/libssl*" + if test=`eval $try 2> /dev/null`; then ssl_libdir=$dir; break; else echo "tried $dir" >&AC_FD_CC ; fi + done + + ac_ssl_libraries="$ssl_libdir" + + ac_ldflags_safe="$LDFLAGS" + ac_libs_safe="$LIBS" + + LDFLAGS="$LDFLAGS -L$ssl_libdir $all_libraries" + LIBS="$LIBS $LIBSSL -lRSAglue -lrsaref" + + AC_TRY_LINK(,void RSAPrivateEncrypt(void);RSAPrivateEncrypt();, + ac_ssl_rsaref="yes" + , + ac_ssl_rsaref="no" + ) + + LDFLAGS="$ac_ldflags_safe" + LIBS="$ac_libs_safe" + + if test "$ac_ssl_includes" = NO || test "$ac_ssl_libraries" = NO; then + have_ssl=no + else + have_ssl=yes; + fi + + ]) + + eval "$ac_cv_have_ssl" + + AC_MSG_RESULT([libraries $ac_ssl_libraries, headers $ac_ssl_includes]) + + AC_MSG_CHECKING([whether OpenSSL uses rsaref]) + AC_MSG_RESULT($ac_ssl_rsaref) + + AC_MSG_CHECKING([for easter eggs]) + AC_MSG_RESULT([none found]) + +else + have_ssl=no +fi + +if test "$have_ssl" = yes; then + AC_MSG_CHECKING(for OpenSSL version) + dnl Check for SSL version + AC_CACHE_VAL(ac_cv_ssl_version, + [ + + cat >conftest.$ac_ext < +#include + int main() { + +#ifndef OPENSSL_VERSION_NUMBER + printf("ssl_version=\\"error\\"\n"); +#else + if (OPENSSL_VERSION_NUMBER < 0x00906000) + printf("ssl_version=\\"old\\"\n"); + else + printf("ssl_version=\\"ok\\"\n"); +#endif + return (0); + } +EOF + + ac_save_CPPFLAGS=$CPPFLAGS + if test "$ac_ssl_includes" != "/usr/include"; then + CPPFLAGS="$CPPFLAGS -I$ac_ssl_includes" + fi + + if AC_TRY_EVAL(ac_link); then + + if eval `./conftest 2>&5`; then + if test $ssl_version = error; then + AC_MSG_ERROR([$ssl_incdir/openssl/opensslv.h doesn't define OPENSSL_VERSION_NUMBER !]) + else + if test $ssl_version = old; then + AC_MSG_WARN([OpenSSL version too old. Upgrade to 0.9.6 at least, see http://www.openssl.org. SSL support disabled.]) + have_ssl=no + fi + fi + ac_cv_ssl_version="ssl_version=$ssl_version" + else + AC_MSG_ERROR([Your system couldn't run a small SSL test program. + Check config.log, and if you can't figure it out, send a mail to + David Faure , attaching your config.log]) + fi + + else + AC_MSG_ERROR([Your system couldn't link a small SSL test program. + Check config.log, and if you can't figure it out, send a mail to + David Faure , attaching your config.log]) + fi + CPPFLAGS=$ac_save_CPPFLAGS + + ]) + + eval "$ac_cv_ssl_version" + AC_MSG_RESULT($ssl_version) +fi + +if test "$have_ssl" != yes; then + LIBSSL=""; +else + AC_DEFINE(HAVE_SSL, 1, [If we are going to use OpenSSL]) + ac_cv_have_ssl="have_ssl=yes \ + ac_ssl_includes=$ac_ssl_includes ac_ssl_libraries=$ac_ssl_libraries ac_ssl_rsaref=$ac_ssl_rsaref" + + + ssl_libraries="$ac_ssl_libraries" + ssl_includes="$ac_ssl_includes" + + if test "$ac_ssl_rsaref" = yes; then + LIBSSL="-lssl -lcrypto -lRSAglue -lrsaref" + fi + + if test $ssl_version = "old"; then + AC_DEFINE(HAVE_OLD_SSL_API, 1, [Define if you have OpenSSL < 0.9.6]) + fi +fi + +SSL_INCLUDES= + +if test "$ssl_includes" = "/usr/include"; then + if test -f /usr/kerberos/include/krb5.h; then + SSL_INCLUDES="-I/usr/kerberos/include" + fi +elif test "$ssl_includes" != "/usr/local/include" && test -n "$ssl_includes"; then + SSL_INCLUDES="-I$ssl_includes" +fi + +if test "$ssl_libraries" = "/usr/lib" || test "$ssl_libraries" = "/usr/local/lib" || test -z "$ssl_libraries" || test "$ssl_libraries" = "NONE"; then + SSL_LDFLAGS="" +else + SSL_LDFLAGS="-L$ssl_libraries -R$ssl_libraries" +fi + +AC_SUBST(SSL_INCLUDES) +AC_SUBST(SSL_LDFLAGS) +AC_SUBST(LIBSSL) +]) + +AC_DEFUN([KDE_CHECK_STRLCPY], +[ + AC_REQUIRE([AC_CHECK_STRLCAT]) + AC_REQUIRE([AC_CHECK_STRLCPY]) + AC_CHECK_SIZEOF(size_t) + AC_CHECK_SIZEOF(unsigned long) + + AC_MSG_CHECKING([sizeof size_t == sizeof unsigned long]) + AC_TRY_COMPILE(,[ + #if SIZEOF_SIZE_T != SIZEOF_UNSIGNED_LONG + choke me + #endif + ],AC_MSG_RESULT([yes]),[ + AC_MSG_RESULT(no) + AC_MSG_ERROR([ + Apparently on your system our assumption sizeof size_t == sizeof unsigned long + does not apply. Please mail kde-devel@kde.org with a description of your system! + ]) + ]) +]) + +AC_DEFUN([KDE_CHECK_BINUTILS], +[ + AC_MSG_CHECKING([if ld supports unversioned version maps]) + + kde_save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS -Wl,--version-script=conftest.map" + echo "{ local: extern \"C++\" { foo }; };" > conftest.map + AC_TRY_LINK([int foo;], +[ +#ifdef __INTEL_COMPILER +icc apparently does not support libtools version-info and version-script +at the same time. Dunno where the bug is, but until somebody figured out, +better disable the optional version scripts. +#endif + + foo = 42; +], kde_supports_versionmaps=yes, kde_supports_versionmaps=no) + LDFLAGS="$kde_save_LDFLAGS" + rm -f conftest.map + AM_CONDITIONAL(include_VERSION_SCRIPT, + [test "$kde_supports_versionmaps" = "yes" && test "$kde_use_debug_code" = "no"]) + + AC_MSG_RESULT($kde_supports_versionmaps) +]) + +AC_DEFUN([AM_PROG_OBJC],[ +AC_CHECK_PROGS(OBJC, gcc, gcc) +test -z "$OBJC" && AC_MSG_ERROR([no acceptable objective-c gcc found in \$PATH]) +if test "x${OBJCFLAGS-unset}" = xunset; then + OBJCFLAGS="-g -O2" +fi +AC_SUBST(OBJCFLAGS) +_AM_IF_OPTION([no-dependencies],, [_AM_DEPENDENCIES(OBJC)]) +]) + +AC_DEFUN([KDE_CHECK_PERL], +[ + KDE_FIND_PATH(perl, PERL, [$bindir $exec_prefix/bin $prefix/bin], [ + AC_MSG_ERROR([No Perl found in your $PATH. +We need perl to generate some code.]) + ]) + AC_SUBST(PERL) +]) + +AC_DEFUN([KDE_CHECK_LARGEFILE], +[ +AC_SYS_LARGEFILE +if test "$ac_cv_sys_file_offset_bits" != no; then + CPPFLAGS="$CPPFLAGS -D_FILE_OFFSET_BITS=$ac_cv_sys_file_offset_bits" +fi + +if test "x$ac_cv_sys_large_files" != "xno"; then + CPPFLAGS="$CPPFLAGS -D_LARGE_FILES=1" +fi + +]) + +dnl A small extension to PKG_CHECK_MODULES (defined in pkg.m4.in) +dnl which allows to search for libs that get installed into the KDE prefix. +dnl +dnl Syntax: KDE_PKG_CHECK_MODULES(KSTUFF, libkexif >= 0.2 glib = 1.3.4, action-if, action-not) +dnl defines KSTUFF_LIBS, KSTUFF_CFLAGS, see pkg-config man page +dnl also defines KSTUFF_PKG_ERRORS on error +AC_DEFUN([KDE_PKG_CHECK_MODULES], [ + + PKG_CONFIG_PATH="$prefix/lib/pkgconfig:$PKG_CONFIG_PATH" + if test "$prefix" != "$kde_libs_prefix"; then + PKG_CONFIG_PATH="$kde_libs_prefix/lib/pkgconfig:$PKG_CONFIG_PATH" + fi + export PKG_CONFIG_PATH + PKG_CHECK_MODULES($1,$2,$3,$4) +]) + +# libtool.m4 - Configure libtool for the host system. -*-Autoconf-*- +## Copyright 1996, 1997, 1998, 1999, 2000, 2001 +## Free Software Foundation, Inc. +## Originally by Gordon Matzigkeit , 1996 +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## As a special exception to the GNU General Public License, if you +## distribute this file as part of a program that contains a +## configuration script generated by Autoconf, you may include it under +## the same distribution terms that you use for the rest of that program. + +# serial 47 AC_PROG_LIBTOOL + + +# AC_PROVIDE_IFELSE(MACRO-NAME, IF-PROVIDED, IF-NOT-PROVIDED) +# ----------------------------------------------------------- +# If this macro is not defined by Autoconf, define it here. +m4_ifdef([AC_PROVIDE_IFELSE], + [], + [m4_define([AC_PROVIDE_IFELSE], + [m4_ifdef([AC_PROVIDE_$1], + [$2], [$3])])]) + + +# AC_PROG_LIBTOOL +# --------------- +AC_DEFUN([AC_PROG_LIBTOOL], +[AC_REQUIRE([_AC_PROG_LIBTOOL])dnl +dnl If AC_PROG_CXX has already been expanded, run AC_LIBTOOL_CXX +dnl immediately, otherwise, hook it in at the end of AC_PROG_CXX. + AC_PROVIDE_IFELSE([AC_PROG_CXX], + [AC_LIBTOOL_CXX], + [define([AC_PROG_CXX], defn([AC_PROG_CXX])[AC_LIBTOOL_CXX + ])]) +dnl And a similar setup for Fortran 77 support + AC_PROVIDE_IFELSE([AC_PROG_F77], + [AC_LIBTOOL_F77], + [define([AC_PROG_F77], defn([AC_PROG_F77])[AC_LIBTOOL_F77 +])]) + +dnl Quote A][M_PROG_GCJ so that aclocal doesn't bring it in needlessly. +dnl If either AC_PROG_GCJ or A][M_PROG_GCJ have already been expanded, run +dnl AC_LIBTOOL_GCJ immediately, otherwise, hook it in at the end of both. + AC_PROVIDE_IFELSE([AC_PROG_GCJ], + [AC_LIBTOOL_GCJ], + [AC_PROVIDE_IFELSE([A][M_PROG_GCJ], + [AC_LIBTOOL_GCJ], + [AC_PROVIDE_IFELSE([LT_AC_PROG_GCJ], + [AC_LIBTOOL_GCJ], + [ifdef([AC_PROG_GCJ], + [define([AC_PROG_GCJ], defn([AC_PROG_GCJ])[AC_LIBTOOL_GCJ])]) + ifdef([A][M_PROG_GCJ], + [define([A][M_PROG_GCJ], defn([A][M_PROG_GCJ])[AC_LIBTOOL_GCJ])]) + ifdef([LT_AC_PROG_GCJ], + [define([LT_AC_PROG_GCJ], + defn([LT_AC_PROG_GCJ])[AC_LIBTOOL_GCJ])])])]) +])])# AC_PROG_LIBTOOL + + +# _AC_PROG_LIBTOOL +# ---------------- +AC_DEFUN([_AC_PROG_LIBTOOL], +[AC_REQUIRE([AC_LIBTOOL_SETUP])dnl +AC_BEFORE([$0],[AC_LIBTOOL_CXX])dnl +AC_BEFORE([$0],[AC_LIBTOOL_F77])dnl +AC_BEFORE([$0],[AC_LIBTOOL_GCJ])dnl + +# This can be used to rebuild libtool when needed +LIBTOOL_DEPS="$ac_aux_dir/ltmain.sh" + +# Always use our own libtool. +LIBTOOL='$(SHELL) $(top_builddir)/libtool --silent' +AC_SUBST(LIBTOOL)dnl + +# Prevent multiple expansion +define([AC_PROG_LIBTOOL], []) +])# _AC_PROG_LIBTOOL + + +# AC_LIBTOOL_SETUP +# ---------------- +AC_DEFUN([AC_LIBTOOL_SETUP], +[AC_PREREQ(2.50)dnl +AC_REQUIRE([AC_ENABLE_SHARED])dnl +AC_REQUIRE([AC_ENABLE_STATIC])dnl +AC_REQUIRE([AC_ENABLE_FAST_INSTALL])dnl +AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_PROG_LD])dnl +AC_REQUIRE([AC_PROG_LD_RELOAD_FLAG])dnl +AC_REQUIRE([AC_PROG_NM])dnl + +AC_REQUIRE([AC_PROG_LN_S])dnl +AC_REQUIRE([AC_DEPLIBS_CHECK_METHOD])dnl +# Autoconf 2.13's AC_OBJEXT and AC_EXEEXT macros only works for C compilers! +AC_REQUIRE([AC_OBJEXT])dnl +AC_REQUIRE([AC_EXEEXT])dnl +dnl + +AC_LIBTOOL_SYS_MAX_CMD_LEN +AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE +AC_LIBTOOL_OBJDIR + +AC_REQUIRE([_LT_AC_SYS_COMPILER])dnl +_LT_AC_PROG_ECHO_BACKSLASH + +case $host_os in +aix3*) + # AIX sometimes has problems with the GCC collect2 program. For some + # reason, if we set the COLLECT_NAMES environment variable, the problems + # vanish in a puff of smoke. + if test "X${COLLECT_NAMES+set}" != Xset; then + COLLECT_NAMES= + export COLLECT_NAMES + fi + ;; +esac + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +Xsed='sed -e s/^X//' +[sed_quote_subst='s/\([\\"\\`$\\\\]\)/\\\1/g'] + +# Same as above, but do not quote variable references. +[double_quote_subst='s/\([\\"\\`\\\\]\)/\\\1/g'] + +# Sed substitution to delay expansion of an escaped shell variable in a +# double_quote_subst'ed string. +delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' + +# Sed substitution to avoid accidental globbing in evaled expressions +no_glob_subst='s/\*/\\\*/g' + +# Constants: +rm="rm -f" + +# Global variables: +default_ofile=libtool +can_build_shared=yes + +# All known linkers require a `.a' archive for static linking (except M$VC, +# which needs '.lib'). +libext=a +ltmain="$ac_aux_dir/ltmain.sh" +ofile="$default_ofile" +with_gnu_ld="$lt_cv_prog_gnu_ld" + +AC_CHECK_TOOL(AR, ar, false) +AC_CHECK_TOOL(RANLIB, ranlib, :) +AC_CHECK_TOOL(STRIP, strip, :) + +old_CC="$CC" +old_CFLAGS="$CFLAGS" + +# Set sane defaults for various variables +test -z "$AR" && AR=ar +test -z "$AR_FLAGS" && AR_FLAGS=cru +test -z "$AS" && AS=as +test -z "$CC" && CC=cc +test -z "$LTCC" && LTCC=$CC +test -z "$DLLTOOL" && DLLTOOL=dlltool +test -z "$LD" && LD=ld +test -z "$LN_S" && LN_S="ln -s" +test -z "$MAGIC_CMD" && MAGIC_CMD=file +test -z "$NM" && NM=nm +test -z "$SED" && SED=sed +test -z "$OBJDUMP" && OBJDUMP=objdump +test -z "$RANLIB" && RANLIB=: +test -z "$STRIP" && STRIP=: +test -z "$ac_objext" && ac_objext=o + +# Determine commands to create old-style static archives. +old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs$old_deplibs' +old_postinstall_cmds='chmod 644 $oldlib' +old_postuninstall_cmds= + +if test -n "$RANLIB"; then + case $host_os in + openbsd*) + old_postinstall_cmds="\$RANLIB -t \$oldlib~$old_postinstall_cmds" + ;; + *) + old_postinstall_cmds="\$RANLIB \$oldlib~$old_postinstall_cmds" + ;; + esac + old_archive_cmds="$old_archive_cmds~\$RANLIB \$oldlib" +fi + +# Only perform the check for file, if the check method requires it +case $deplibs_check_method in +file_magic*) + if test "$file_magic_cmd" = '$MAGIC_CMD'; then + AC_PATH_MAGIC + fi + ;; +esac + +AC_PROVIDE_IFELSE([AC_LIBTOOL_DLOPEN], enable_dlopen=yes, enable_dlopen=no) +AC_PROVIDE_IFELSE([AC_LIBTOOL_WIN32_DLL], +enable_win32_dll=yes, enable_win32_dll=no) + +AC_ARG_ENABLE([libtool-lock], + [AC_HELP_STRING([--disable-libtool-lock], + [avoid locking (might break parallel builds)])]) +test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes + +AC_ARG_WITH([pic], + [AC_HELP_STRING([--with-pic], + [try to use only PIC/non-PIC objects @<:@default=use both@:>@])], + [pic_mode="$withval"], + [pic_mode=default]) +test -z "$pic_mode" && pic_mode=default + +# Use C for the default configuration in the libtool script +tagname= +AC_LIBTOOL_LANG_C_CONFIG +_LT_AC_TAGCONFIG +])# AC_LIBTOOL_SETUP + + +# _LT_AC_SYS_COMPILER +# ------------------- +AC_DEFUN([_LT_AC_SYS_COMPILER], +[AC_REQUIRE([AC_PROG_CC])dnl + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# Allow CC to be a program name with arguments. +compiler=$CC +])# _LT_AC_SYS_COMPILER + + +# _LT_AC_SYS_LIBPATH_AIX +# ---------------------- +# Links a minimal program and checks the executable +# for the system default hardcoded library path. In most cases, +# this is /usr/lib:/lib, but when the MPI compilers are used +# the location of the communication and MPI libs are included too. +# If we don't find anything, use the default library path according +# to the aix ld manual. +AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX], +[AC_LINK_IFELSE(AC_LANG_PROGRAM,[ +aix_libpath=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } +}'` +# Check for a 64-bit object if we didn't find anything. +if test -z "$aix_libpath"; then aix_libpath=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } +}'`; fi],[]) +if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib"; fi +])# _LT_AC_SYS_LIBPATH_AIX + + +# _LT_AC_SHELL_INIT(ARG) +# ---------------------- +AC_DEFUN([_LT_AC_SHELL_INIT], +[ifdef([AC_DIVERSION_NOTICE], + [AC_DIVERT_PUSH(AC_DIVERSION_NOTICE)], + [AC_DIVERT_PUSH(NOTICE)]) +$1 +AC_DIVERT_POP +])# _LT_AC_SHELL_INIT + + +# _LT_AC_PROG_ECHO_BACKSLASH +# -------------------------- +# Add some code to the start of the generated configure script which +# will find an echo command which doesn't interpret backslashes. +AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH], +[_LT_AC_SHELL_INIT([ +# Check that we are running under the correct shell. +SHELL=${CONFIG_SHELL-/bin/sh} + +case X$ECHO in +X*--fallback-echo) + # Remove one level of quotation (which was required for Make). + ECHO=`echo "$ECHO" | sed 's,\\\\\[$]\\[$]0,'[$]0','` + ;; +esac + +echo=${ECHO-echo} +if test "X[$]1" = X--no-reexec; then + # Discard the --no-reexec flag, and continue. + shift +elif test "X[$]1" = X--fallback-echo; then + # Avoid inline document here, it may be left over + : +elif test "X`($echo '\t') 2>/dev/null`" = 'X\t' ; then + # Yippee, $echo works! + : +else + # Restart under the correct shell. + exec $SHELL "[$]0" --no-reexec ${1+"[$]@"} +fi + +if test "X[$]1" = X--fallback-echo; then + # used as fallback echo + shift + cat </dev/null && + echo_test_string="`eval $cmd`" && + (test "X$echo_test_string" = "X$echo_test_string") 2>/dev/null + then + break + fi + done +fi + +if test "X`($echo '\t') 2>/dev/null`" = 'X\t' && + echo_testing_string=`($echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + : +else + # The Solaris, AIX, and Digital Unix default echo programs unquote + # backslashes. This makes it impossible to quote backslashes using + # echo "$something" | sed 's/\\/\\\\/g' + # + # So, first we look for a working echo in the user's PATH. + + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for dir in $PATH /usr/ucb; do + IFS="$lt_save_ifs" + if (test -f $dir/echo || test -f $dir/echo$ac_exeext) && + test "X`($dir/echo '\t') 2>/dev/null`" = 'X\t' && + echo_testing_string=`($dir/echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + echo="$dir/echo" + break + fi + done + IFS="$lt_save_ifs" + + if test "X$echo" = Xecho; then + # We didn't find a better echo, so look for alternatives. + if test "X`(print -r '\t') 2>/dev/null`" = 'X\t' && + echo_testing_string=`(print -r "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + # This shell has a builtin print -r that does the trick. + echo='print -r' + elif (test -f /bin/ksh || test -f /bin/ksh$ac_exeext) && + test "X$CONFIG_SHELL" != X/bin/ksh; then + # If we have ksh, try running configure again with it. + ORIGINAL_CONFIG_SHELL=${CONFIG_SHELL-/bin/sh} + export ORIGINAL_CONFIG_SHELL + CONFIG_SHELL=/bin/ksh + export CONFIG_SHELL + exec $CONFIG_SHELL "[$]0" --no-reexec ${1+"[$]@"} + else + # Try using printf. + echo='printf %s\n' + if test "X`($echo '\t') 2>/dev/null`" = 'X\t' && + echo_testing_string=`($echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + # Cool, printf works + : + elif echo_testing_string=`($ORIGINAL_CONFIG_SHELL "[$]0" --fallback-echo '\t') 2>/dev/null` && + test "X$echo_testing_string" = 'X\t' && + echo_testing_string=`($ORIGINAL_CONFIG_SHELL "[$]0" --fallback-echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + CONFIG_SHELL=$ORIGINAL_CONFIG_SHELL + export CONFIG_SHELL + SHELL="$CONFIG_SHELL" + export SHELL + echo="$CONFIG_SHELL [$]0 --fallback-echo" + elif echo_testing_string=`($CONFIG_SHELL "[$]0" --fallback-echo '\t') 2>/dev/null` && + test "X$echo_testing_string" = 'X\t' && + echo_testing_string=`($CONFIG_SHELL "[$]0" --fallback-echo "$echo_test_string") 2>/dev/null` && + test "X$echo_testing_string" = "X$echo_test_string"; then + echo="$CONFIG_SHELL [$]0 --fallback-echo" + else + # maybe with a smaller string... + prev=: + + for cmd in 'echo test' 'sed 2q "[$]0"' 'sed 10q "[$]0"' 'sed 20q "[$]0"' 'sed 50q "[$]0"'; do + if (test "X$echo_test_string" = "X`eval $cmd`") 2>/dev/null + then + break + fi + prev="$cmd" + done + + if test "$prev" != 'sed 50q "[$]0"'; then + echo_test_string=`eval $prev` + export echo_test_string + exec ${ORIGINAL_CONFIG_SHELL-${CONFIG_SHELL-/bin/sh}} "[$]0" ${1+"[$]@"} + else + # Oops. We lost completely, so just stick with echo. + echo=echo + fi + fi + fi + fi +fi +fi + +# Copy echo and quote the copy suitably for passing to libtool from +# the Makefile, instead of quoting the original, which is used later. +ECHO=$echo +if test "X$ECHO" = "X$CONFIG_SHELL [$]0 --fallback-echo"; then + ECHO="$CONFIG_SHELL \\\$\[$]0 --fallback-echo" +fi + +AC_SUBST(ECHO) +])])# _LT_AC_PROG_ECHO_BACKSLASH + + +# _LT_AC_LOCK +# ----------- +AC_DEFUN([_LT_AC_LOCK], +[AC_ARG_ENABLE([libtool-lock], + [AC_HELP_STRING([--disable-libtool-lock], + [avoid locking (might break parallel builds)])]) +test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes + +# Some flags need to be propagated to the compiler or linker for good +# libtool support. +case $host in +ia64-*-hpux*) + # Find out which ABI we are using. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.$ac_objext` in + *ELF-32*) + HPUX_IA64_MODE="32" + ;; + *ELF-64*) + HPUX_IA64_MODE="64" + ;; + esac + fi + rm -rf conftest* + ;; +*-*-irix6*) + # Find out which ABI we are using. + echo '[#]line __oline__ "configure"' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + if test "$lt_cv_prog_gnu_ld" = yes; then + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -melf32bsmip" + ;; + *N32*) + LD="${LD-ld} -melf32bmipn32" + ;; + *64-bit*) + LD="${LD-ld} -melf64bmip" + ;; + esac + else + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -32" + ;; + *N32*) + LD="${LD-ld} -n32" + ;; + *64-bit*) + LD="${LD-ld} -64" + ;; + esac + fi + fi + rm -rf conftest* + ;; + +x86_64-*linux*|ppc*-*linux*|powerpc*-*linux*|s390*-*linux*|sparc*-*linux*) + # Find out which ABI we are using. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case "`/usr/bin/file conftest.o`" in + *32-bit*) + LINUX_64_MODE="32" + case $host in + x86_64-*linux*) + LD="${LD-ld} -m elf_i386" + ;; + ppc64-*linux*) + LD="${LD-ld} -m elf32ppclinux" + ;; + s390x-*linux*) + LD="${LD-ld} -m elf_s390" + ;; + sparc64-*linux*) + LD="${LD-ld} -m elf32_sparc" + ;; + esac + ;; + *64-bit*) + LINUX_64_MODE="64" + case $host in + x86_64-*linux*) + LD="${LD-ld} -m elf_x86_64" + ;; + ppc*-*linux*|powerpc*-*linux*) + LD="${LD-ld} -m elf64ppc" + ;; + s390*-*linux*) + LD="${LD-ld} -m elf64_s390" + ;; + sparc*-*linux*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; + +*-*-sco3.2v5*) + # On SCO OpenServer 5, we need -belf to get full-featured binaries. + SAVE_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -belf" + AC_CACHE_CHECK([whether the C compiler needs -belf], lt_cv_cc_needs_belf, + [AC_LANG_PUSH(C) + AC_TRY_LINK([],[],[lt_cv_cc_needs_belf=yes],[lt_cv_cc_needs_belf=no]) + AC_LANG_POP]) + if test x"$lt_cv_cc_needs_belf" != x"yes"; then + # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf + CFLAGS="$SAVE_CFLAGS" + fi + ;; +AC_PROVIDE_IFELSE([AC_LIBTOOL_WIN32_DLL], +[*-*-cygwin* | *-*-mingw* | *-*-pw32*) + AC_CHECK_TOOL(DLLTOOL, dlltool, false) + AC_CHECK_TOOL(AS, as, false) + AC_CHECK_TOOL(OBJDUMP, objdump, false) + ;; + ]) +esac + +need_locks="$enable_libtool_lock" + +])# _LT_AC_LOCK + + +# AC_LIBTOOL_COMPILER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [OUTPUT-FILE], [ACTION-SUCCESS], [ACTION-FAILURE]) +# ---------------------------------------------------------------- +# Check whether the given compiler option works +AC_DEFUN([AC_LIBTOOL_COMPILER_OPTION], +[AC_REQUIRE([LT_AC_PROG_SED]) +AC_CACHE_CHECK([$1], [$2], + [$2=no + ifelse([$4], , [ac_outfile=conftest.$ac_objext], [ac_outfile=$4]) + printf "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$3" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}? :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:__oline__: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:__oline__: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + if test ! -s conftest.err; then + $2=yes + fi + fi + $rm conftest* +]) + +if test x"[$]$2" = xyes; then + ifelse([$5], , :, [$5]) +else + ifelse([$6], , :, [$6]) +fi +])# AC_LIBTOOL_COMPILER_OPTION + + +# AC_LIBTOOL_LINKER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [ACTION-SUCCESS], [ACTION-FAILURE]) +# ------------------------------------------------------------ +# Check whether the given compiler option works +AC_DEFUN([AC_LIBTOOL_LINKER_OPTION], +[AC_CACHE_CHECK([$1], [$2], + [$2=no + save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS $3" + printf "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&AS_MESSAGE_LOG_FD + else + $2=yes + fi + fi + $rm conftest* + LDFLAGS="$save_LDFLAGS" +]) + +if test x"[$]$2" = xyes; then + ifelse([$4], , :, [$4]) +else + ifelse([$5], , :, [$5]) +fi +])# AC_LIBTOOL_LINKER_OPTION + + +# AC_LIBTOOL_SYS_MAX_CMD_LEN +# -------------------------- +AC_DEFUN([AC_LIBTOOL_SYS_MAX_CMD_LEN], +[# find the maximum length of command line arguments +AC_MSG_CHECKING([the maximum length of command line arguments]) +AC_CACHE_VAL([lt_cv_sys_max_cmd_len], [dnl + i=0 + testring="ABCD" + + case $build_os in + msdosdjgpp*) + # On DJGPP, this test can blow up pretty badly due to problems in libc + # (any single argument exceeding 2000 bytes causes a buffer overrun + # during glob expansion). Even if it were fixed, the result of this + # check would be larger than it should be. + lt_cv_sys_max_cmd_len=12288; # 12K is about right + ;; + + gnu*) + # Under GNU Hurd, this test is not required because there is + # no limit to the length of command line arguments. + # Libtool will interpret -1 as no limit whatsoever + lt_cv_sys_max_cmd_len=-1; + ;; + + cygwin* | mingw*) + # On Win9x/ME, this test blows up -- it succeeds, but takes + # about 5 minutes as the teststring grows exponentially. + # Worse, since 9x/ME are not pre-emptively multitasking, + # you end up with a "frozen" computer, even though with patience + # the test eventually succeeds (with a max line length of 256k). + # Instead, let's just punt: use the minimum linelength reported by + # all of the supported platforms: 8192 (on NT/2K/XP). + lt_cv_sys_max_cmd_len=8192; + ;; + + *) + # If test is not a shell built-in, we'll probably end up computing a + # maximum length that is only half of the actual maximum length, but + # we can't tell. + while (test "X"`$CONFIG_SHELL [$]0 --fallback-echo "X$testring" 2>/dev/null` \ + = "XX$testring") >/dev/null 2>&1 && + new_result=`expr "X$testring" : ".*" 2>&1` && + lt_cv_sys_max_cmd_len=$new_result && + test $i != 17 # 1/2 MB should be enough + do + i=`expr $i + 1` + testring=$testring$testring + done + testring= + # Add a significant safety factor because C++ compilers can tack on massive + # amounts of additional arguments before passing them to the linker. + # It appears as though 1/2 is a usable value. + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` + ;; + esac +]) +if test -n $lt_cv_sys_max_cmd_len ; then + AC_MSG_RESULT($lt_cv_sys_max_cmd_len) +else + AC_MSG_RESULT(none) +fi +])# AC_LIBTOOL_SYS_MAX_CMD_LEN + + +# _LT_AC_CHECK_DLFCN +# -------------------- +AC_DEFUN([_LT_AC_CHECK_DLFCN], +[AC_CHECK_HEADERS(dlfcn.h)dnl +])# _LT_AC_CHECK_DLFCN + + +# _LT_AC_TRY_DLOPEN_SELF (ACTION-IF-TRUE, ACTION-IF-TRUE-W-USCORE, +# ACTION-IF-FALSE, ACTION-IF-CROSS-COMPILING) +# ------------------------------------------------------------------ +AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF], +[AC_REQUIRE([_LT_AC_CHECK_DLFCN])dnl +if test "$cross_compiling" = yes; then : + [$4] +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext < +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +#ifdef __cplusplus +extern "C" void exit (int); +#endif + +void fnord() { int i=42;} +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + /* dlclose (self); */ + } + + exit (status); +}] +EOF + if AC_TRY_EVAL(ac_link) && test -s conftest${ac_exeext} 2>/dev/null; then + (./conftest; exit; ) 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) $1 ;; + x$lt_dlneed_uscore) $2 ;; + x$lt_unknown|x*) $3 ;; + esac + else : + # compilation failed + $3 + fi +fi +rm -fr conftest* +])# _LT_AC_TRY_DLOPEN_SELF + + +# AC_LIBTOOL_DLOPEN_SELF +# ------------------- +AC_DEFUN([AC_LIBTOOL_DLOPEN_SELF], +[AC_REQUIRE([_LT_AC_CHECK_DLFCN])dnl +if test "x$enable_dlopen" != xyes; then + enable_dlopen=unknown + enable_dlopen_self=unknown + enable_dlopen_self_static=unknown +else + lt_cv_dlopen=no + lt_cv_dlopen_libs= + + case $host_os in + beos*) + lt_cv_dlopen="load_add_on" + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ;; + + mingw* | pw32*) + lt_cv_dlopen="LoadLibrary" + lt_cv_dlopen_libs= + ;; + + cygwin*) + lt_cv_dlopen="dlopen" + lt_cv_dlopen_libs= + ;; + + darwin*) + # if libdl is installed we need to link against it + AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"],[ + lt_cv_dlopen="dyld" + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ]) + ;; + + *) + AC_CHECK_FUNC([shl_load], + [lt_cv_dlopen="shl_load"], + [AC_CHECK_LIB([dld], [shl_load], + [lt_cv_dlopen="shl_load" lt_cv_dlopen_libs="-dld"], + [AC_CHECK_FUNC([dlopen], + [lt_cv_dlopen="dlopen"], + [AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"], + [AC_CHECK_LIB([svld], [dlopen], + [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-lsvld"], + [AC_CHECK_LIB([dld], [dld_link], + [lt_cv_dlopen="dld_link" lt_cv_dlopen_libs="-dld"]) + ]) + ]) + ]) + ]) + ]) + ;; + esac + + if test "x$lt_cv_dlopen" != xno; then + enable_dlopen=yes + else + enable_dlopen=no + fi + + case $lt_cv_dlopen in + dlopen) + save_CPPFLAGS="$CPPFLAGS" + test "x$ac_cv_header_dlfcn_h" = xyes && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" + + save_LDFLAGS="$LDFLAGS" + eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" + + save_LIBS="$LIBS" + LIBS="$lt_cv_dlopen_libs $LIBS" + + AC_CACHE_CHECK([whether a program can dlopen itself], + lt_cv_dlopen_self, [dnl + _LT_AC_TRY_DLOPEN_SELF( + lt_cv_dlopen_self=yes, lt_cv_dlopen_self=yes, + lt_cv_dlopen_self=no, lt_cv_dlopen_self=cross) + ]) + + if test "x$lt_cv_dlopen_self" = xyes; then + LDFLAGS="$LDFLAGS $link_static_flag" + AC_CACHE_CHECK([whether a statically linked program can dlopen itself], + lt_cv_dlopen_self_static, [dnl + _LT_AC_TRY_DLOPEN_SELF( + lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=yes, + lt_cv_dlopen_self_static=no, lt_cv_dlopen_self_static=cross) + ]) + fi + + CPPFLAGS="$save_CPPFLAGS" + LDFLAGS="$save_LDFLAGS" + LIBS="$save_LIBS" + ;; + esac + + case $lt_cv_dlopen_self in + yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; + *) enable_dlopen_self=unknown ;; + esac + + case $lt_cv_dlopen_self_static in + yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; + *) enable_dlopen_self_static=unknown ;; + esac +fi +])# AC_LIBTOOL_DLOPEN_SELF + + +# AC_LIBTOOL_PROG_CC_C_O([TAGNAME]) +# --------------------------------- +# Check to see if options -c and -o are simultaneously supported by compiler +AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O], +[AC_REQUIRE([_LT_AC_SYS_COMPILER])dnl +AC_CACHE_CHECK([if $compiler supports -c -o file.$ac_objext], + [_LT_AC_TAGVAR(lt_cv_prog_compiler_c_o, $1)], + [_LT_AC_TAGVAR(lt_cv_prog_compiler_c_o, $1)=no + $rm -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + printf "$lt_simple_compile_test_code" > conftest.$ac_ext + + # According to Tom Tromey, Ian Lance Taylor reported there are C compilers + # that will create temporary files in the current directory regardless of + # the output directory. Thus, making CWD read-only will cause this test + # to fail, enabling locking or at least warning the user not to do parallel + # builds. + chmod -w . + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}? :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:__oline__: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:__oline__: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + if test ! -s out/conftest.err; then + _LT_AC_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes + fi + fi + chmod u+w . + $rm conftest* out/* + rmdir out + cd .. + rmdir conftest + $rm conftest* +]) +])# AC_LIBTOOL_PROG_CC_C_O + + +# AC_LIBTOOL_SYS_HARD_LINK_LOCKS([TAGNAME]) +# ----------------------------------------- +# Check to see if we can do hard links to lock some files if needed +AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], +[AC_REQUIRE([_LT_AC_LOCK])dnl + +hard_links="nottested" +if test "$_LT_AC_TAGVAR(lt_cv_prog_compiler_c_o, $1)" = no && test "$need_locks" != no; then + # do not overwrite the value of need_locks provided by the user + AC_MSG_CHECKING([if we can lock with hard links]) + hard_links=yes + $rm conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + AC_MSG_RESULT([$hard_links]) + if test "$hard_links" = no; then + AC_MSG_WARN([`$CC' does not support `-c -o', so `make -j' may be unsafe]) + need_locks=warn + fi +else + need_locks=no +fi +])# AC_LIBTOOL_SYS_HARD_LINK_LOCKS + + +# AC_LIBTOOL_OBJDIR +# ----------------- +AC_DEFUN([AC_LIBTOOL_OBJDIR], +[AC_CACHE_CHECK([for objdir], [lt_cv_objdir], +[rm -f .libs 2>/dev/null +mkdir .libs 2>/dev/null +if test -d .libs; then + lt_cv_objdir=.libs +else + # MS-DOS does not allow filenames that begin with a dot. + lt_cv_objdir=_libs +fi +rmdir .libs 2>/dev/null]) +objdir=$lt_cv_objdir +])# AC_LIBTOOL_OBJDIR + + +# AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH([TAGNAME]) +# ---------------------------------------------- +# Check hardcoding attributes. +AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], +[AC_MSG_CHECKING([how to hardcode library paths into programs]) +_LT_AC_TAGVAR(hardcode_action, $1)= +if test -n "$_LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)" || \ + test -n "$_LT_AC_TAGVAR(runpath_var $1)" || \ + test "X$_LT_AC_TAGVAR(hardcode_automatic, $1)"="Xyes" ; then + + # We can hardcode non-existant directories. + if test "$_LT_AC_TAGVAR(hardcode_direct, $1)" != no && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test "$_LT_AC_TAGVAR(hardcode_shlibpath_var, $1)" != no && + test "$_LT_AC_TAGVAR(hardcode_minus_L, $1)" != no; then + # Linking always hardcodes the temporary library directory. + _LT_AC_TAGVAR(hardcode_action, $1)=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + _LT_AC_TAGVAR(hardcode_action, $1)=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + _LT_AC_TAGVAR(hardcode_action, $1)=unsupported +fi +AC_MSG_RESULT([$_LT_AC_TAGVAR(hardcode_action, $1)]) + +if test "$_LT_AC_TAGVAR(hardcode_action, $1)" = relink; then + # Fast installation is not supported + enable_fast_install=no +elif test "$shlibpath_overrides_runpath" = yes || + test "$enable_shared" = no; then + # Fast installation is not necessary + enable_fast_install=needless +fi +])# AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH + + +# AC_LIBTOOL_SYS_LIB_STRIP +# ------------------------ +AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP], +[striplib= +old_striplib= +AC_MSG_CHECKING([whether stripping libraries is possible]) +if test -n "$STRIP" && $STRIP -V 2>&1 | grep "GNU strip" >/dev/null; then + test -z "$old_striplib" && old_striplib="$STRIP --strip-debug" + test -z "$striplib" && striplib="$STRIP --strip-unneeded" + AC_MSG_RESULT([yes]) +else +# FIXME - insert some real tests, host_os isn't really good enough + case $host_os in + darwin*) + if test -n "$STRIP" ; then + striplib="$STRIP -x" + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) +fi + ;; + *) + AC_MSG_RESULT([no]) + ;; + esac +fi +])# AC_LIBTOOL_SYS_LIB_STRIP + + +# AC_LIBTOOL_SYS_DYNAMIC_LINKER +# ----------------------------- +# PORTME Fill in your ld.so characteristics +AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER], +[AC_MSG_CHECKING([dynamic linker characteristics]) +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext=".so" +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + +case $host_os in +aix3*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='${libname}${release}${shared_ext}$major' + ;; + +aix4* | aix5*) + version_type=linux + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test "$host_cpu" = ia64; then + # AIX 5 supports IA64 + library_names_spec='${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext}$versuffix $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line `#! .'. This would cause the generated library to + # depend on `.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[[01]] | aix4.[[01]].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | ${CC} -E - | grep yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # AIX (on Power*) has no versioning support, so currently we can not hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + if test "$aix_use_runtimelinking" = yes; then + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + else + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='${libname}${release}.a $libname.a' + soname_spec='${libname}${release}${shared_ext}$major' + fi + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`$echo "X$lib" | $Xsed -e '\''s%^.*/\([[^/]]*\)\.ixlibrary$%\1%'\''`; test $rm /sys/libs/${libname}_ixlibrary.a; $show "(cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a)"; (cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a) || exit 1; done' + ;; + +beos*) + library_names_spec='${libname}${shared_ext}' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi4*) + version_type=linux + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32*) + version_type=windows + shrext=".dll" + need_version=no + need_lib_prefix=no + + case $GCC,$host_os in + yes,cygwin* | yes,mingw* | yes,pw32*) + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \${file}`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i;echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $rm \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}' + sys_lib_search_path_spec="/usr/lib /lib/w32api /lib /usr/local/lib" + ;; + mingw*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='${libname}`echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}' + sys_lib_search_path_spec=`$CC -print-search-dirs | grep "^libraries:" | $SED -e "s/^libraries://" -e "s,=/,/,g"` + if echo "$sys_lib_search_path_spec" | [grep ';[c-zC-Z]:/' >/dev/null]; then + # It is most probably a Windows format PATH printed by + # mingw gcc, but we are running on Cygwin. Gcc prints its search + # path with ; separators, and with drive letters. We can handle the + # drive letters (cygwin fileutils understands them), so leave them, + # especially as we might pass files found there to a mingw objdump, + # which wouldn't understand a cygwinified path. Ahh. + sys_lib_search_path_spec=`echo "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`echo "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}' + ;; + esac + ;; + + *) + library_names_spec='${libname}`echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext} $libname.lib' + ;; + esac + dynamic_linker='Win32 ld.exe' + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${versuffix}$shared_ext ${libname}${release}${major}$shared_ext ${libname}$shared_ext' + soname_spec='${libname}${release}${major}$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext='$(test .$module = .yes && echo .so || echo .dylib)' + # Apple's gcc prints 'gcc -print-search-dirs' doesn't operate the same. + if test "$GCC" = yes; then + sys_lib_search_path_spec=`$CC -print-search-dirs | tr "\n" "$PATH_SEPARATOR" | sed -e 's/libraries:/@libraries:/' | tr "@" "\n" | grep "^libraries:" | sed -e "s/^libraries://" -e "s,=/,/,g" -e "s,$PATH_SEPARATOR, ,g" -e "s,.*,& /lib /usr/lib /usr/local/lib,g"` + else + sys_lib_search_path_spec='/lib /usr/lib /usr/local/lib' + fi + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname$shared_ext' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd1*) + dynamic_linker=no + ;; + +freebsd*-gnu*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='GNU ld.so' + ;; + +freebsd*) + objformat=`test -x /usr/bin/objformat && /usr/bin/objformat || echo aout` + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='${libname}${release}${shared_ext}$versuffix $libname${shared_ext}$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[01]* | freebsdelf3.[01]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + *) # from 3.2 on + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + esac + ;; + +gnu*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case "$host_cpu" in + ia64*) + shrext='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + if test "X$HPUX_IA64_MODE" = X32; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + fi + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + hppa*64*) + shrext='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555. + postinstall_cmds='chmod 555 $lib' + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test "$lt_cv_prog_gnu_ld" = yes; then + version_type=linux + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} $libname${shared_ext}' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}" + sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +# This must be Linux ELF. +linux*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + libsuff= + if test "x$LINUX_64_MODE" = x64; then + # Some platforms are per default 64-bit, so there's no /lib64 + if test -d /lib64; then + libsuff=64 + fi + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + sys_lib_dlsearch_path_spec="/lib${libsuff} /usr/lib${libsuff}" + sys_lib_search_path_spec="/lib${libsuff} /usr/lib${libsuff} /usr/local/lib${libsuff}" + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +nto-qnx*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +openbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + case $host_os in + openbsd2.[[89]] | openbsd2.[[89]].*) + shlibpath_overrides_runpath=no + ;; + *) + shlibpath_overrides_runpath=yes + ;; + esac + else + shlibpath_overrides_runpath=yes + fi + ;; + +os2*) + libname_spec='$name' + shrext=".dll" + need_lib_prefix=no + library_names_spec='$libname${shared_ext} $libname.a' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=LIBPATH + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec" + ;; + +sco3.2v5*) + version_type=osf + soname_spec='${libname}${release}${shared_ext}$major' + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + shlibpath_var=LD_LIBRARY_PATH + ;; + +solaris*) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test "$with_gnu_ld" = yes; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + export_dynamic_flag_spec='${wl}-Blargedynsym' + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec ;then + version_type=linux + library_names_spec='$libname${shared_ext}.$versuffix $libname${shared_ext}.$major $libname${shared_ext}' + soname_spec='$libname${shared_ext}.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +uts4*) + version_type=linux + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +AC_MSG_RESULT([$dynamic_linker]) +test "$dynamic_linker" = no && can_build_shared=no +])# AC_LIBTOOL_SYS_DYNAMIC_LINKER + + +# _LT_AC_TAGCONFIG +# ---------------- +AC_DEFUN([_LT_AC_TAGCONFIG], +[AC_ARG_WITH([tags], + [AC_HELP_STRING([--with-tags@<:@=TAGS@:>@], + [include additional configurations @<:@automatic@:>@])], + [tagnames="$withval"]) + +if test -f "$ltmain" && test -n "$tagnames"; then + if test ! -f "${ofile}"; then + AC_MSG_WARN([output file `$ofile' does not exist]) + fi + + if test -z "$LTCC"; then + eval "`$SHELL ${ofile} --config | grep '^LTCC='`" + if test -z "$LTCC"; then + AC_MSG_WARN([output file `$ofile' does not look like a libtool script]) + else + AC_MSG_WARN([using `LTCC=$LTCC', extracted from `$ofile']) + fi + fi + + # Extract list of available tagged configurations in $ofile. + # Note that this assumes the entire list is on one line. + available_tags=`grep "^available_tags=" "${ofile}" | $SED -e 's/available_tags=\(.*$\)/\1/' -e 's/\"//g'` + + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for tagname in $tagnames; do + IFS="$lt_save_ifs" + # Check whether tagname contains only valid characters + case `$echo "X$tagname" | $Xsed -e 's:[[-_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890,/]]::g'` in + "") ;; + *) AC_MSG_ERROR([invalid tag name: $tagname]) + ;; + esac + + if grep "^# ### BEGIN LIBTOOL TAG CONFIG: $tagname$" < "${ofile}" > /dev/null + then + AC_MSG_ERROR([tag name \"$tagname\" already exists]) + fi + + # Update the list of available tags. + if test -n "$tagname"; then + echo appending configuration tag \"$tagname\" to $ofile + + case $tagname in + CXX) + if test -n "$CXX" && test "X$CXX" != "Xno"; then + AC_LIBTOOL_LANG_CXX_CONFIG + else + tagname="" + fi + ;; + + F77) + if test -n "$F77" && test "X$F77" != "Xno"; then + AC_LIBTOOL_LANG_F77_CONFIG + else + tagname="" + fi + ;; + + GCJ) + if test -n "$GCJ" && test "X$GCJ" != "Xno"; then + AC_LIBTOOL_LANG_GCJ_CONFIG + else + tagname="" + fi + ;; + + RC) + AC_LIBTOOL_LANG_RC_CONFIG + ;; + + *) + AC_MSG_ERROR([Unsupported tag name: $tagname]) + ;; + esac + + # Append the new tag name to the list of available tags. + if test -n "$tagname" ; then + available_tags="$available_tags $tagname" + fi + fi + done + IFS="$lt_save_ifs" + + # Now substitute the updated list of available tags. + if eval "sed -e 's/^available_tags=.*\$/available_tags=\"$available_tags\"/' \"$ofile\" > \"${ofile}T\""; then + mv "${ofile}T" "$ofile" + chmod +x "$ofile" + else + rm -f "${ofile}T" + AC_MSG_ERROR([unable to update list of available tagged configurations.]) + fi +fi +])# _LT_AC_TAGCONFIG + + +# AC_LIBTOOL_DLOPEN +# ----------------- +# enable checks for dlopen support +AC_DEFUN([AC_LIBTOOL_DLOPEN], + [AC_BEFORE([$0],[AC_LIBTOOL_SETUP]) +])# AC_LIBTOOL_DLOPEN + + +# AC_LIBTOOL_WIN32_DLL +# -------------------- +# declare package support for building win32 dll's +AC_DEFUN([AC_LIBTOOL_WIN32_DLL], +[AC_BEFORE([$0], [AC_LIBTOOL_SETUP]) +])# AC_LIBTOOL_WIN32_DLL + + +# AC_ENABLE_SHARED([DEFAULT]) +# --------------------------- +# implement the --enable-shared flag +# DEFAULT is either `yes' or `no'. If omitted, it defaults to `yes'. +AC_DEFUN([AC_ENABLE_SHARED], +[define([AC_ENABLE_SHARED_DEFAULT], ifelse($1, no, no, yes))dnl +AC_ARG_ENABLE([shared], + [AC_HELP_STRING([--enable-shared@<:@=PKGS@:>@], + [build shared libraries @<:@default=]AC_ENABLE_SHARED_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_shared=yes ;; + no) enable_shared=no ;; + *) + enable_shared=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for pkg in $enableval; do + IFS="$lt_save_ifs" + if test "X$pkg" = "X$p"; then + enable_shared=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac], + [enable_shared=]AC_ENABLE_SHARED_DEFAULT) +])# AC_ENABLE_SHARED + + +# AC_DISABLE_SHARED +# ----------------- +#- set the default shared flag to --disable-shared +AC_DEFUN([AC_DISABLE_SHARED], +[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl +AC_ENABLE_SHARED(no) +])# AC_DISABLE_SHARED + + +# AC_ENABLE_STATIC([DEFAULT]) +# --------------------------- +# implement the --enable-static flag +# DEFAULT is either `yes' or `no'. If omitted, it defaults to `yes'. +AC_DEFUN([AC_ENABLE_STATIC], +[define([AC_ENABLE_STATIC_DEFAULT], ifelse($1, no, no, yes))dnl +AC_ARG_ENABLE([static], + [AC_HELP_STRING([--enable-static@<:@=PKGS@:>@], + [build static libraries @<:@default=]AC_ENABLE_STATIC_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_static=yes ;; + no) enable_static=no ;; + *) + enable_static=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for pkg in $enableval; do + IFS="$lt_save_ifs" + if test "X$pkg" = "X$p"; then + enable_static=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac], + [enable_static=]AC_ENABLE_STATIC_DEFAULT) +])# AC_ENABLE_STATIC + + +# AC_DISABLE_STATIC +# ----------------- +# set the default static flag to --disable-static +AC_DEFUN([AC_DISABLE_STATIC], +[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl +AC_ENABLE_STATIC(no) +])# AC_DISABLE_STATIC + + +# AC_ENABLE_FAST_INSTALL([DEFAULT]) +# --------------------------------- +# implement the --enable-fast-install flag +# DEFAULT is either `yes' or `no'. If omitted, it defaults to `yes'. +AC_DEFUN([AC_ENABLE_FAST_INSTALL], +[define([AC_ENABLE_FAST_INSTALL_DEFAULT], ifelse($1, no, no, yes))dnl +AC_ARG_ENABLE([fast-install], + [AC_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@], + [optimize for fast installation @<:@default=]AC_ENABLE_FAST_INSTALL_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_fast_install=yes ;; + no) enable_fast_install=no ;; + *) + enable_fast_install=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR," + for pkg in $enableval; do + IFS="$lt_save_ifs" + if test "X$pkg" = "X$p"; then + enable_fast_install=yes + fi + done + IFS="$lt_save_ifs" + ;; + esac], + [enable_fast_install=]AC_ENABLE_FAST_INSTALL_DEFAULT) +])# AC_ENABLE_FAST_INSTALL + + +# AC_DISABLE_FAST_INSTALL +# ----------------------- +# set the default to --disable-fast-install +AC_DEFUN([AC_DISABLE_FAST_INSTALL], +[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl +AC_ENABLE_FAST_INSTALL(no) +])# AC_DISABLE_FAST_INSTALL + + +# AC_LIBTOOL_PICMODE([MODE]) +# -------------------------- +# implement the --with-pic flag +# MODE is either `yes' or `no'. If omitted, it defaults to `both'. +AC_DEFUN([AC_LIBTOOL_PICMODE], +[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl +pic_mode=ifelse($#,1,$1,default) +])# AC_LIBTOOL_PICMODE + + +# AC_PROG_EGREP +# ------------- +# This is predefined starting with Autoconf 2.54, so this conditional +# definition can be removed once we require Autoconf 2.54 or later. +m4_ifndef([AC_PROG_EGREP], [AC_DEFUN([AC_PROG_EGREP], +[AC_CACHE_CHECK([for egrep], [ac_cv_prog_egrep], + [if echo a | (grep -E '(a|b)') >/dev/null 2>&1 + then ac_cv_prog_egrep='grep -E' + else ac_cv_prog_egrep='egrep' + fi]) + EGREP=$ac_cv_prog_egrep + AC_SUBST([EGREP]) +])]) + + +# AC_PATH_TOOL_PREFIX +# ------------------- +# find a file program which can recognise shared library +AC_DEFUN([AC_PATH_TOOL_PREFIX], +[AC_REQUIRE([AC_PROG_EGREP])dnl +AC_MSG_CHECKING([for $1]) +AC_CACHE_VAL(lt_cv_path_MAGIC_CMD, +[case $MAGIC_CMD in +[[\\/*] | ?:[\\/]*]) + lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD="$MAGIC_CMD" + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR +dnl $ac_dummy forces splitting on constant user-supplied paths. +dnl POSIX.2 word splitting is done only on the output of word expansions, +dnl not every word. This closes a longstanding sh security hole. + ac_dummy="ifelse([$2], , $PATH, [$2])" + for ac_dir in $ac_dummy; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$1; then + lt_cv_path_MAGIC_CMD="$ac_dir/$1" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex="`expr \"$deplibs_check_method\" : \"file_magic \(.*\)\"`" + MAGIC_CMD="$lt_cv_path_MAGIC_CMD" + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +EOF + fi ;; + esac + fi + break + fi + done + IFS="$lt_save_ifs" + MAGIC_CMD="$lt_save_MAGIC_CMD" + ;; +esac]) +MAGIC_CMD="$lt_cv_path_MAGIC_CMD" +if test -n "$MAGIC_CMD"; then + AC_MSG_RESULT($MAGIC_CMD) +else + AC_MSG_RESULT(no) +fi +])# AC_PATH_TOOL_PREFIX + + +# AC_PATH_MAGIC +# ------------- +# find a file program which can recognise a shared library +AC_DEFUN([AC_PATH_MAGIC], +[AC_PATH_TOOL_PREFIX(${ac_tool_prefix}file, /usr/bin$PATH_SEPARATOR$PATH) +if test -z "$lt_cv_path_MAGIC_CMD"; then + if test -n "$ac_tool_prefix"; then + AC_PATH_TOOL_PREFIX(file, /usr/bin$PATH_SEPARATOR$PATH) + else + MAGIC_CMD=: + fi +fi +])# AC_PATH_MAGIC + + +# AC_PROG_LD +# ---------- +# find the pathname to the GNU or non-GNU linker +AC_DEFUN([AC_PROG_LD], +[AC_ARG_WITH([gnu-ld], + [AC_HELP_STRING([--with-gnu-ld], + [assume the C compiler uses GNU ld @<:@default=no@:>@])], + [test "$withval" = no || with_gnu_ld=yes], + [with_gnu_ld=no]) +AC_REQUIRE([LT_AC_PROG_SED])dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +ac_prog=ld +if test "$GCC" = yes; then + # Check if gcc -print-prog-name=ld gives a path. + AC_MSG_CHECKING([for ld used by $CC]) + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [[\\/]]* | ?:[[\\/]]*) + re_direlt='/[[^/]][[^/]]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`echo $ac_prog| $SED 's%\\\\%/%g'` + while echo $ac_prog | grep "$re_direlt" > /dev/null 2>&1; do + ac_prog=`echo $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD="$ac_prog" + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test "$with_gnu_ld" = yes; then + AC_MSG_CHECKING([for GNU ld]) +else + AC_MSG_CHECKING([for non-GNU ld]) +fi +AC_CACHE_VAL(lt_cv_path_LD, +[if test -z "$LD"; then + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD="$ac_dir/$ac_prog" + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some GNU ld's only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &1 /dev/null; then + case $host_cpu in + i*86 ) + # Not sure whether the presence of OpenBSD here was a mistake. + # Let's accept both of them until this is cleared up. + lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD)/i[[3-9]]86 (compact )?demand paged shared library' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` + ;; + esac + else + lt_cv_deplibs_check_method=pass_all + fi + ;; + +gnu*) + lt_cv_deplibs_check_method=pass_all + ;; + +hpux10.20* | hpux11*) + lt_cv_file_magic_cmd=/usr/bin/file + case "$host_cpu" in + ia64*) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|ELF-[[0-9]][[0-9]]) shared object file - IA64' + lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so + ;; + hppa*64*) + [lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF-[0-9][0-9]) shared object file - PA-RISC [0-9].[0-9]'] + lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl + ;; + *) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|PA-RISC[[0-9]].[[0-9]]) shared library' + lt_cv_file_magic_test_file=/usr/lib/libc.sl + ;; + esac + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + irix5* | nonstopux*) + # this will be overridden with pass_all, but let us keep it just in case + lt_cv_deplibs_check_method="file_magic ELF 32-bit MSB dynamic lib MIPS - version 1" + ;; + *) + case $LD in + *-32|*"-32 ") libmagic=32-bit;; + *-n32|*"-n32 ") libmagic=N32;; + *-64|*"-64 ") libmagic=64-bit;; + *) libmagic=never-match;; + esac + # this will be overridden with pass_all, but let us keep it just in case + lt_cv_deplibs_check_method="file_magic ELF ${libmagic} MSB mips-[[1234]] dynamic lib MIPS - version 1" + ;; + esac + lt_cv_file_magic_test_file=`echo /lib${libsuff}/libc.so*` + lt_cv_deplibs_check_method=pass_all + ;; + +# This must be Linux ELF. +linux*) + case $host_cpu in + alpha* | hppa* | i*86 | ia64* | m68* | mips* | powerpc* | sparc* | s390* | sh* | x86_64* ) + lt_cv_deplibs_check_method=pass_all ;; + # the debian people say, arm and glibc 2.3.1 works for them with pass_all + arm* ) + lt_cv_deplibs_check_method=pass_all ;; + *) + # glibc up to 2.1.1 does not perform some relocations on ARM + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )' ;; + esac + lt_cv_file_magic_test_file=`echo /lib/libc.so* /lib/libc-*.so` + ;; + +netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ > /dev/null; then + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|_pic\.a)$' + fi + ;; + +newos6*) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (executable|dynamic lib)' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=/usr/lib/libnls.so + ;; + +nto-qnx*) + lt_cv_deplibs_check_method=unknown + ;; + +openbsd*) + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB shared object' + else + lt_cv_deplibs_check_method='file_magic OpenBSD.* shared library' + fi + ;; + +osf3* | osf4* | osf5*) + # this will be overridden with pass_all, but let us keep it just in case + lt_cv_deplibs_check_method='file_magic COFF format alpha shared library' + lt_cv_file_magic_test_file=/shlib/libc.so + lt_cv_deplibs_check_method=pass_all + ;; + +sco3.2v5*) + lt_cv_deplibs_check_method=pass_all + ;; + +solaris*) + lt_cv_deplibs_check_method=pass_all + lt_cv_file_magic_test_file=/lib/libc.so + ;; + +sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + case $host_vendor in + motorola) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib) M[[0-9]][[0-9]]* Version [[0-9]]' + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` + ;; + ncr) + lt_cv_deplibs_check_method=pass_all + ;; + sequent) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )' + ;; + sni) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method="file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB dynamic lib" + lt_cv_file_magic_test_file=/lib/libc.so + ;; + siemens) + lt_cv_deplibs_check_method=pass_all + ;; + esac + ;; + +sysv5OpenUNIX8* | sysv5UnixWare7* | sysv5uw[[78]]* | unixware7* | sysv4*uw2*) + lt_cv_deplibs_check_method=pass_all + ;; +esac +]) +file_magic_cmd=$lt_cv_file_magic_cmd +deplibs_check_method=$lt_cv_deplibs_check_method +test -z "$deplibs_check_method" && deplibs_check_method=unknown +])# AC_DEPLIBS_CHECK_METHOD + + +# AC_PROG_NM +# ---------- +# find the pathname to a BSD-compatible name lister +AC_DEFUN([AC_PROG_NM], +[AC_CACHE_CHECK([for BSD-compatible nm], lt_cv_path_NM, +[if test -n "$NM"; then + # Let the user override the test. + lt_cv_path_NM="$NM" +else + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in $PATH /usr/ccs/bin /usr/ucb /bin; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + tmp_nm="$ac_dir/${ac_tool_prefix}nm" + if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext" ; then + # Check to see if the nm accepts a BSD-compat flag. + # Adding the `sed 1q' prevents false positives on HP-UX, which says: + # nm: unknown option "B" ignored + # Tru64's nm complains that /dev/null is an invalid object file + case `"$tmp_nm" -B /dev/null 2>&1 | sed '1q'` in + */dev/null* | *'Invalid file or object type'*) + lt_cv_path_NM="$tmp_nm -B" + break + ;; + *) + case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in + */dev/null*) + lt_cv_path_NM="$tmp_nm -p" + break + ;; + *) + lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but + continue # so that we can try to find one that supports BSD flags + ;; + esac + esac + fi + done + IFS="$lt_save_ifs" + test -z "$lt_cv_path_NM" && lt_cv_path_NM=nm +fi]) +NM="$lt_cv_path_NM" +])# AC_PROG_NM + + +# AC_CHECK_LIBM +# ------------- +# check for math library +AC_DEFUN([AC_CHECK_LIBM], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +LIBM= +case $host in +*-*-beos* | *-*-cygwin* | *-*-pw32* | *-*-darwin*) + # These system don't have libm, or don't need it + ;; +*-ncr-sysv4.3*) + AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM="-lmw") + AC_CHECK_LIB(m, cos, LIBM="$LIBM -lm") + ;; +*) + AC_CHECK_LIB(m, cos, LIBM="-lm") + ;; +esac +])# AC_CHECK_LIBM + + +# AC_LIBLTDL_CONVENIENCE([DIRECTORY]) +# ----------------------------------- +# sets LIBLTDL to the link flags for the libltdl convenience library and +# LTDLINCL to the include flags for the libltdl header and adds +# --enable-ltdl-convenience to the configure arguments. Note that LIBLTDL +# and LTDLINCL are not AC_SUBSTed, nor is AC_CONFIG_SUBDIRS called. If +# DIRECTORY is not provided, it is assumed to be `libltdl'. LIBLTDL will +# be prefixed with '${top_builddir}/' and LTDLINCL will be prefixed with +# '${top_srcdir}/' (note the single quotes!). If your package is not +# flat and you're not using automake, define top_builddir and +# top_srcdir appropriately in the Makefiles. +AC_DEFUN([AC_LIBLTDL_CONVENIENCE], +[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl + case $enable_ltdl_convenience in + no) AC_MSG_ERROR([this package needs a convenience libltdl]) ;; + "") enable_ltdl_convenience=yes + ac_configure_args="$ac_configure_args --enable-ltdl-convenience" ;; + esac + LIBLTDL='${top_builddir}/'ifelse($#,1,[$1],['libltdl'])/libltdlc.la + LTDLINCL='-I${top_srcdir}/'ifelse($#,1,[$1],['libltdl']) + # For backwards non-gettext consistent compatibility... + INCLTDL="$LTDLINCL" +])# AC_LIBLTDL_CONVENIENCE + + +# AC_LIBLTDL_INSTALLABLE([DIRECTORY]) +# ----------------------------------- +# sets LIBLTDL to the link flags for the libltdl installable library and +# LTDLINCL to the include flags for the libltdl header and adds +# --enable-ltdl-install to the configure arguments. Note that LIBLTDL +# and LTDLINCL are not AC_SUBSTed, nor is AC_CONFIG_SUBDIRS called. If +# DIRECTORY is not provided and an installed libltdl is not found, it is +# assumed to be `libltdl'. LIBLTDL will be prefixed with '${top_builddir}/' +# and LTDLINCL will be prefixed with '${top_srcdir}/' (note the single +# quotes!). If your package is not flat and you're not using automake, +# define top_builddir and top_srcdir appropriately in the Makefiles. +# In the future, this macro may have to be called after AC_PROG_LIBTOOL. +AC_DEFUN([AC_LIBLTDL_INSTALLABLE], +[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl + AC_CHECK_LIB(ltdl, lt_dlinit, + [test x"$enable_ltdl_install" != xyes && enable_ltdl_install=no], + [if test x"$enable_ltdl_install" = xno; then + AC_MSG_WARN([libltdl not installed, but installation disabled]) + else + enable_ltdl_install=yes + fi + ]) + if test x"$enable_ltdl_install" = x"yes"; then + ac_configure_args="$ac_configure_args --enable-ltdl-install" + LIBLTDL='${top_builddir}/'ifelse($#,1,[$1],['libltdl'])/libltdl.la + LTDLINCL='-I${top_srcdir}/'ifelse($#,1,[$1],['libltdl']) + else + ac_configure_args="$ac_configure_args --enable-ltdl-install=no" + LIBLTDL="-lltdl" + LTDLINCL= + fi + # For backwards non-gettext consistent compatibility... + INCLTDL="$LTDLINCL" +])# AC_LIBLTDL_INSTALLABLE + + +# AC_LIBTOOL_CXX +# -------------- +# enable support for C++ libraries +AC_DEFUN([AC_LIBTOOL_CXX], +[AC_REQUIRE([_LT_AC_LANG_CXX]) +])# AC_LIBTOOL_CXX + + +# _LT_AC_LANG_CXX +# --------------- +AC_DEFUN([_LT_AC_LANG_CXX], +[AC_REQUIRE([AC_PROG_CXX]) +AC_REQUIRE([AC_PROG_CXXCPP]) +_LT_AC_SHELL_INIT([tagnames=${tagnames+${tagnames},}CXX]) +])# _LT_AC_LANG_CXX + + +# AC_LIBTOOL_F77 +# -------------- +# enable support for Fortran 77 libraries +AC_DEFUN([AC_LIBTOOL_F77], +[AC_REQUIRE([_LT_AC_LANG_F77]) +])# AC_LIBTOOL_F77 + + +# _LT_AC_LANG_F77 +# --------------- +AC_DEFUN([_LT_AC_LANG_F77], +[AC_REQUIRE([AC_PROG_F77]) +_LT_AC_SHELL_INIT([tagnames=${tagnames+${tagnames},}F77]) +])# _LT_AC_LANG_F77 + + +# AC_LIBTOOL_GCJ +# -------------- +# enable support for GCJ libraries +AC_DEFUN([AC_LIBTOOL_GCJ], +[AC_REQUIRE([_LT_AC_LANG_GCJ]) +])# AC_LIBTOOL_GCJ + + +# _LT_AC_LANG_GCJ +# --------------- +AC_DEFUN([_LT_AC_LANG_GCJ], +[AC_PROVIDE_IFELSE([AC_PROG_GCJ],[], + [AC_PROVIDE_IFELSE([A][M_PROG_GCJ],[], + [AC_PROVIDE_IFELSE([LT_AC_PROG_GCJ],[], + [ifdef([AC_PROG_GCJ],[AC_REQUIRE([AC_PROG_GCJ])], + [ifdef([A][M_PROG_GCJ],[AC_REQUIRE([A][M_PROG_GCJ])], + [AC_REQUIRE([A][C_PROG_GCJ_OR_A][M_PROG_GCJ])])])])])]) +_LT_AC_SHELL_INIT([tagnames=${tagnames+${tagnames},}GCJ]) +])# _LT_AC_LANG_GCJ + + +# AC_LIBTOOL_RC +# -------------- +# enable support for Windows resource files +AC_DEFUN([AC_LIBTOOL_RC], +[AC_REQUIRE([LT_AC_PROG_RC]) +_LT_AC_SHELL_INIT([tagnames=${tagnames+${tagnames},}RC]) +])# AC_LIBTOOL_RC + + +# AC_LIBTOOL_LANG_C_CONFIG +# ------------------------ +# Ensure that the configuration vars for the C compiler are +# suitably defined. Those variables are subsequently used by +# AC_LIBTOOL_CONFIG to write the compiler configuration to `libtool'. +AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG], [_LT_AC_LANG_C_CONFIG]) +AC_DEFUN([_LT_AC_LANG_C_CONFIG], +[lt_save_CC="$CC" +AC_LANG_PUSH(C) + +# Source file extension for C test sources. +ac_ext=c + +# Object file extension for compiled C test sources. +objext=o +_LT_AC_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;\n" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(){return(0);}\n' + +_LT_AC_SYS_COMPILER + +# +# Check for any special shared library compilation flags. +# +_LT_AC_TAGVAR(lt_prog_cc_shlib, $1)= +if test "$GCC" = no; then + case $host_os in + sco3.2v5*) + _LT_AC_TAGVAR(lt_prog_cc_shlib, $1)='-belf' + ;; + esac +fi +if test -n "$_LT_AC_TAGVAR(lt_prog_cc_shlib, $1)"; then + AC_MSG_WARN([`$CC' requires `$_LT_AC_TAGVAR(lt_prog_cc_shlib, $1)' to build shared libraries]) + if echo "$old_CC $old_CFLAGS " | grep "[[ ]]$]_LT_AC_TAGVAR(lt_prog_cc_shlib, $1)[[[ ]]" >/dev/null; then : + else + AC_MSG_WARN([add `$_LT_AC_TAGVAR(lt_prog_cc_shlib, $1)' to the CC or CFLAGS env variable and reconfigure]) + _LT_AC_TAGVAR(lt_cv_prog_cc_can_build_shared, $1)=no + fi +fi + + +# +# Check to make sure the static flag actually works. +# +AC_LIBTOOL_LINKER_OPTION([if $compiler static flag $_LT_AC_TAGVAR(lt_prog_compiler_static, $1) works], + _LT_AC_TAGVAR(lt_prog_compiler_static_works, $1), + $_LT_AC_TAGVAR(lt_prog_compiler_static, $1), + [], + [_LT_AC_TAGVAR(lt_prog_compiler_static, $1)=]) + + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +AC_LIBTOOL_PROG_COMPILER_NO_RTTI($1) +AC_LIBTOOL_PROG_COMPILER_PIC($1) +AC_LIBTOOL_PROG_CC_C_O($1) +AC_LIBTOOL_SYS_HARD_LINK_LOCKS($1) +AC_LIBTOOL_PROG_LD_SHLIBS($1) +AC_LIBTOOL_SYS_DYNAMIC_LINKER($1) +AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH($1) +AC_LIBTOOL_SYS_LIB_STRIP +AC_LIBTOOL_DLOPEN_SELF($1) + +# Report which librarie types wil actually be built +AC_MSG_CHECKING([if libtool supports shared libraries]) +AC_MSG_RESULT([$can_build_shared]) + +AC_MSG_CHECKING([whether to build shared libraries]) +test "$can_build_shared" = "no" && enable_shared=no + +# On AIX, shared libraries and static libraries use the same namespace, and +# are all built from PIC. +case "$host_os" in +aix3*) + test "$enable_shared" = yes && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + +aix4*) + if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then + test "$enable_shared" = yes && enable_static=no + fi + ;; + darwin* | rhapsody*) + if test "$GCC" = yes; then + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + case "$host_os" in + rhapsody* | darwin1.[[012]]) + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-Wl,-undefined -Wl,suppress' + ;; + *) # Darwin 1.3 on + if test -z ${MACOSX_DEPLOYMENT_TARGET} ; then + allow_undefined_flag='-Wl,-flat_namespace -Wl,-undefined -Wl,suppress' + else + case ${MACOSX_DEPLOYMENT_TARGET} in + 10.[012]) + allow_undefined_flag='-Wl,-flat_namespace -Wl,-undefined -Wl,suppress' + ;; + 10.*) + allow_undefined_flag='-Wl,-undefined -Wl,dynamic_lookup' + ;; + esac + fi + ;; + esac + output_verbose_link_cmd='echo' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -dynamiclib $allow_undefined_flag -o $lib $compiler_flags $libobjs $deplibs -install_name $rpath/$soname $verstring' + _LT_AC_TAGVAR(module_cmds, $1)='$CC $allow_undefined_flag -o $lib -bundle $compiler_flags $libobjs $deplibs' + # Don't fix this by using the ld -exported_symbols_list flag, it doesn't exist in older darwin ld's + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC -dynamiclib $allow_undefined_flag -o $lib $compiler_flags $libobjs $deplibs -install_name $rpath/$soname $verstring~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + _LT_AC_TAGVAR(module_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC $allow_undefined_flag -o $lib -bundle $compiler_flags $libobjs $deplibs~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_automatic, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='-all_load $convenience' + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; +esac +AC_MSG_RESULT([$enable_shared]) + +AC_MSG_CHECKING([whether to build static libraries]) +# Make sure either enable_shared or enable_static is yes. +test "$enable_shared" = yes || enable_static=yes +AC_MSG_RESULT([$enable_static]) + +AC_LIBTOOL_CONFIG($1) + +AC_LANG_POP +CC="$lt_save_CC" +])# AC_LIBTOOL_LANG_C_CONFIG + + +# AC_LIBTOOL_LANG_CXX_CONFIG +# -------------------------- +# Ensure that the configuration vars for the C compiler are +# suitably defined. Those variables are subsequently used by +# AC_LIBTOOL_CONFIG to write the compiler configuration to `libtool'. +AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG], [_LT_AC_LANG_CXX_CONFIG(CXX)]) +AC_DEFUN([_LT_AC_LANG_CXX_CONFIG], +[AC_LANG_PUSH(C++) +AC_REQUIRE([AC_PROG_CXX]) +AC_REQUIRE([AC_PROG_CXXCPP]) + +_LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_AC_TAGVAR(allow_undefined_flag, $1)= +_LT_AC_TAGVAR(always_export_symbols, $1)=no +_LT_AC_TAGVAR(archive_expsym_cmds, $1)= +_LT_AC_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_AC_TAGVAR(hardcode_direct, $1)=no +_LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1)= +_LT_AC_TAGVAR(hardcode_libdir_separator, $1)= +_LT_AC_TAGVAR(hardcode_minus_L, $1)=no +_LT_AC_TAGVAR(hardcode_automatic, $1)=no +_LT_AC_TAGVAR(module_cmds, $1)= +_LT_AC_TAGVAR(module_expsym_cmds, $1)= +_LT_AC_TAGVAR(link_all_deplibs, $1)=unknown +_LT_AC_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_AC_TAGVAR(no_undefined_flag, $1)= +_LT_AC_TAGVAR(whole_archive_flag_spec, $1)= +_LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Dependencies to place before and after the object being linked: +_LT_AC_TAGVAR(predep_objects, $1)= +_LT_AC_TAGVAR(postdep_objects, $1)= +_LT_AC_TAGVAR(predeps, $1)= +_LT_AC_TAGVAR(postdeps, $1)= +_LT_AC_TAGVAR(compiler_lib_search_path, $1)= + +# Source file extension for C++ test sources. +ac_ext=cc + +# Object file extension for compiled C++ test sources. +objext=o +_LT_AC_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;\n" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(int, char *[]) { return(0); }\n' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_AC_SYS_COMPILER + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_LD=$LD +lt_save_GCC=$GCC +GCC=$GXX +lt_save_with_gnu_ld=$with_gnu_ld +lt_save_path_LD=$lt_cv_path_LD +if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then + lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx +else + unset lt_cv_prog_gnu_ld +fi +if test -n "${lt_cv_path_LDCXX+set}"; then + lt_cv_path_LD=$lt_cv_path_LDCXX +else + unset lt_cv_path_LD +fi +test -z "${LDCXX+set}" || LD=$LDCXX +CC=${CXX-"c++"} +compiler=$CC +_LT_AC_TAGVAR(compiler, $1)=$CC +cc_basename=`$echo X"$compiler" | $Xsed -e 's%^.*/%%'` + +# We don't want -fno-exception wen compiling C++ code, so set the +# no_builtin_flag separately +if test "$GXX" = yes; then + _LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' +else + _LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= +fi + +if test "$GXX" = yes; then + # Set up default GNU C++ configuration + + AC_PROG_LD + + # Check if GNU C++ uses GNU ld as the underlying linker, since the + # archiving commands below assume that GNU ld is being used. + if test "$with_gnu_ld" = yes; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared -nostdlib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}--rpath ${wl}$libdir' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + + # If archive_cmds runs LD, not CC, wlarc should be empty + # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to + # investigate it a little bit more. (MM) + wlarc='${wl}' + + # ancient GNU ld didn't support --whole-archive et. al. + if eval "`$CC -print-prog-name=ld` --help 2>&1" | \ + grep 'no-whole-archive' > /dev/null; then + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + else + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)= + fi + else + with_gnu_ld=no + wlarc= + + # A generic and very simple default shared library creation + # command for GNU C++ for the case where it uses the native + # linker, instead of GNU ld. If possible, this setting should + # overridden to take advantage of the native linker features on + # the platform it is being used on. + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects -o $lib' + fi + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "\-L"' + +else + GXX=no + with_gnu_ld=no + wlarc= +fi + +# PORTME: fill in a description of your system's C++ link characteristics +AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) +_LT_AC_TAGVAR(ld_shlibs, $1)=yes +case $host_os in + aix3*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + aix4* | aix5*) + if test "$host_cpu" = ia64; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag="" + else + # KDE requires run time linking. Make it the default. + aix_use_runtimelinking=yes + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_AC_TAGVAR(archive_cmds, $1)='' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + + if test "$GXX" = yes; then + case $host_os in aix4.[012]|aix4.[012].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && \ + strings "$collect2name" | grep resolve_lib_name >/dev/null + then + # We have reworked collect2 + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + else + # We have old collect2 + _LT_AC_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)= + fi + esac + shared_flag='-shared' + else + # not using gcc + if test "$host_cpu" = ia64; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test "$aix_use_runtimelinking" = yes; then + shared_flag='-qmkshrobj ${wl}-G' + else + shared_flag='-qmkshrobj' + fi + fi + fi + + # Let the compiler handle the export list. + _LT_AC_TAGVAR(always_export_symbols, $1)=no + if test "$aix_use_runtimelinking" = yes; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-berok' + # Determine the default libpath from the value encoded in an empty executable. + _LT_AC_SYS_LIBPATH_AIX + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath" + + _LT_AC_TAGVAR(archive_cmds, $1)="\$CC"' -o $output_objdir/$soname $compiler_flags $libobjs $deplibs `if test "x${allow_undefined_flag}" != "x"; then echo "${wl}${allow_undefined_flag}"; else :; fi` '" $shared_flag" + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="\$CC"' -o $output_objdir/$soname $compiler_flags $libobjs $deplibs `if test "x${allow_undefined_flag}" != "x"; then echo "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag" + else + if test "$host_cpu" = ia64; then + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $libdir:/usr/lib:/lib' + _LT_AC_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $compiler_flags $libobjs $deplibs ${wl}${allow_undefined_flag} '"\${wl}$no_entry_flag \${wl}$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an empty executable. + _LT_AC_SYS_LIBPATH_AIX + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_AC_TAGVAR(no_undefined_flag, $1)=' ${wl}-bernotok' + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-berok' + # -bexpall does not export symbols beginning with underscore (_) + _LT_AC_TAGVAR(always_export_symbols, $1)=yes + # Exported symbols can be pulled into shared objects from archives + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)=' ' + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=yes + # This is similar to how AIX traditionally builds it's shared libraries. + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $compiler_flags $libobjs $deplibs ${wl}-bE:$export_symbols ${wl}-bnoentry${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname' + fi + fi + ;; + chorus*) + case $cc_basename in + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + cygwin* | mingw* | pw32*) + # _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(allow_undefined_flag, $1)=no + _LT_AC_TAGVAR(always_export_symbols, $1)=no + _LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + + if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects -o $output_objdir/$soname ${wl}--image-base=0x10000000 ${wl}--out-implib,$lib' + # If the export-symbols file already is a .def file (1st line + # is EXPORTS), use it as is; otherwise, prepend... + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared -nostdlib $output_objdir/$soname.def $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects -o $output_objdir/$soname ${wl}--image-base=0x10000000 ${wl}--out-implib,$lib' + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + darwin* | rhapsody*) + if test "$GXX" = yes; then + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + case "$host_os" in + rhapsody* | darwin1.[[012]]) + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-Wl,-undefined -Wl,suppress' + ;; + *) # Darwin 1.3 on + if test -z ${MACOSX_DEPLOYMENT_TARGET} ; then + allow_undefined_flag='-Wl,-flat_namespace -Wl,-undefined -Wl,suppress' + else + case ${MACOSX_DEPLOYMENT_TARGET} in + 10.[012]) + allow_undefined_flag='-Wl,-flat_namespace -Wl,-undefined -Wl,suppress' + ;; + 10.*) + allow_undefined_flag='-Wl,-undefined -Wl,dynamic_lookup' + ;; + esac + fi + ;; + esac + lt_int_apple_cc_single_mod=no + output_verbose_link_cmd='echo' + if $CC -dumpspecs 2>&1 | grep 'single_module' >/dev/null ; then + lt_int_apple_cc_single_mod=yes + fi + if test "X$lt_int_apple_cc_single_mod" = Xyes ; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -dynamiclib -single_module $allow_undefined_flag -o $lib $compiler_flags $libobjs $deplibs -install_name $rpath/$soname $verstring' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -r ${wl}-bind_at_load -keep_private_externs -nostdlib -o ${lib}-master.o $libobjs~$CC -dynamiclib $allow_undefined_flag -o $lib ${lib}-master.o $compiler_flags $deplibs -install_name $rpath/$soname $verstring' + fi + _LT_AC_TAGVAR(module_cmds, $1)='$CC ${wl}-bind_at_load $allow_undefined_flag -o $lib -bundle $compiler_flags $libobjs $deplibs' + + # Don't fix this by using the ld -exported_symbols_list flag, it doesn't exist in older darwin ld's + if test "X$lt_int_apple_cc_single_mod" = Xyes ; then + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC -dynamiclib -single_module $allow_undefined_flag -o $lib $compiler_flags $libobjs $deplibs -install_name $rpath/$soname $verstring~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + else + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC -r ${wl}-bind_at_load -keep_private_externs -nostdlib -o ${lib}-master.o $libobjs~$CC -dynamiclib $allow_undefined_flag -o $lib ${lib}-master.o $compiler_flags $deplibs -install_name $rpath/$soname $verstring~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + fi + _LT_AC_TAGVAR(module_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC $allow_undefined_flag -o $lib -bundle $compiler_flags $libobjs $deplibs~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_automatic, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='-all_load $convenience' + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + dgux*) + case $cc_basename in + ec++) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + ghcx) + # Green Hills C++ Compiler + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + freebsd[12]*) + # C++ shared libraries reported to be fairly broken before switch to ELF + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + freebsd-elf*) + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + ;; + freebsd*) + # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF + # conventions + _LT_AC_TAGVAR(ld_shlibs, $1)=yes + ;; + gnu*) + ;; + hpux9*) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + + case $cc_basename in + CC) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + aCC) + _LT_AC_TAGVAR(archive_cmds, $1)='$rm $output_objdir/$soname~$CC -b ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | egrep "\-L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + *) + if test "$GXX" = yes; then + _LT_AC_TAGVAR(archive_cmds, $1)='$rm $output_objdir/$soname~$CC -shared -nostdlib -fPIC ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + else + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + hpux10*|hpux11*) + if test $with_gnu_ld = no; then + case "$host_cpu" in + hppa*64*) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1)='+b $libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + ia64*) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + ;; + *) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + ;; + esac + fi + case "$host_cpu" in + hppa*64*) + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + ia64*) + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + ;; + *) + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + ;; + esac + + case $cc_basename in + CC) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + aCC) + case "$host_cpu" in + hppa*64*|ia64*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -b +h $soname -o $lib $linker_flags $libobjs $deplibs' + ;; + *) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects' + ;; + esac + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | grep "\-L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + *) + if test "$GXX" = yes; then + if test $with_gnu_ld = no; then + case "$host_cpu" in + ia64*|hppa*64*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -b +h $soname -o $lib $linker_flags $libobjs $deplibs' + ;; + *) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects' + ;; + esac + fi + else + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + irix5* | irix6*) + case $cc_basename in + CC) + # SGI C++ + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -all -multigot $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${objdir}/so_locations -o $lib' + + # Archives containing C++ object files must be created using + # "CC -ar", where "CC" is the IRIX C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC -ar -WR,-u -o $oldlib $oldobjs' + ;; + *) + if test "$GXX" = yes; then + if test "$with_gnu_ld" = no; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${objdir}/so_locations -o $lib' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` -o $lib' + fi + fi + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + ;; + esac + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + linux*) + case $cc_basename in + KCC) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_AC_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects --soname $soname -o \$templib; mv \$templib $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects --soname $soname -o \$templib ${wl}-retain-symbols-file,$export_symbols; mv \$templib $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | grep "ld"`; rm -f libconftest$shared_ext; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}--rpath,$libdir' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + + # Archives containing C++ object files must be created using + # "CC -Bstatic", where "CC" is the KAI C++ compiler. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' + ;; + icpc) + # Intel C++ + with_gnu_ld=yes + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive$convenience ${wl}--no-whole-archive' + ;; + cxx) + # Compaq C++ + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname $wl$soname -o $lib ${wl}-retain-symbols-file $wl$export_symbols' + + runpath_var=LD_RUN_PATH + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "ld"`; templist=`echo $templist | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + esac + ;; + lynxos*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + m88k*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + mvs*) + case $cc_basename in + cxx) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags' + wlarc= + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + fi + # Workaround some broken pre-1.5 toolchains + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"' + ;; + osf3*) + case $cc_basename in + KCC) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_AC_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects --soname $soname -o \$templib; mv \$templib $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Archives containing C++ object files must be created using + # "CC -Bstatic", where "CC" is the KAI C++ compiler. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' + + ;; + RCC) + # Rational C++ 2.4.1 + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + cxx) + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname $soname `test -n "$verstring" && echo ${wl}-set_version $verstring` -update_registry ${objdir}/so_locations -o $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "ld" | grep -v "ld:"`; templist=`echo $templist | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + *) + if test "$GXX" = yes && test "$with_gnu_ld" = no; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib ${allow_undefined_flag} $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${objdir}/so_locations -o $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "\-L"' + + else + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + osf4* | osf5*) + case $cc_basename in + KCC) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_AC_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects --soname $soname -o \$templib; mv \$templib $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Archives containing C++ object files must be created using + # the KAI C++ compiler. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC -o $oldlib $oldobjs' + ;; + RCC) + # Rational C++ 2.4.1 + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + cxx) + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects -msym -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${objdir}/so_locations -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~ + echo "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects -msym -soname $soname -Wl,-input -Wl,$lib.exp `test -n "$verstring" && echo -set_version $verstring` -update_registry $objdir/so_locations -o $lib~ + $rm $lib.exp' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "ld" | grep -v "ld:"`; templist=`echo $templist | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + ;; + *) + if test "$GXX" = yes && test "$with_gnu_ld" = no; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib ${allow_undefined_flag} $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${objdir}/so_locations -o $lib' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep "\-L"' + + else + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + psos*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + sco*) + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + case $cc_basename in + CC) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + sunos4*) + case $cc_basename in + CC) + # Sun C++ 4.x + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + lcc) + # Lucid + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + solaris*) + case $cc_basename in + CC) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_AC_TAGVAR(no_undefined_flag, $1)=' -zdefs' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -G${allow_undefined_flag} -nolib -h$soname -o $lib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $CC -G${allow_undefined_flag} -nolib ${wl}-M ${wl}$lib.exp -h$soname -o $lib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects~$rm $lib.exp' + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) + # The C++ compiler is used as linker so we must use $wl + # flag to pass the commands to the underlying system + # linker. + # Supported since Solaris 2.6 (maybe 2.5.1?) + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract' + ;; + esac + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -G $CFLAGS -v conftest.$objext 2>&1 | grep "\-[[LR]]"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; echo $list' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' + ;; + gcx) + # Green Hills C++ Compiler + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-h $wl$soname -o $lib' + + # The C++ compiler must be used to create the archive. + _LT_AC_TAGVAR(old_archive_cmds, $1)='$CC $LDFLAGS -archive -o $oldlib $oldobjs' + ;; + *) + # GNU C++ compiler with Solaris linker + if test "$GXX" = yes && test "$with_gnu_ld" = no; then + _LT_AC_TAGVAR(no_undefined_flag, $1)=' ${wl}-z ${wl}defs' + if $CC --version | grep -v '^2\.7' > /dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $LDFLAGS $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-h $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $CC -shared -nostdlib ${wl}-M $wl$lib.exp -o $lib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects~$rm $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd="$CC -shared $CFLAGS -v conftest.$objext 2>&1 | grep \"\-L\"" + else + # g++ 2.7 appears to require `-G' NOT `-shared' on this + # platform. + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -G -nostdlib $LDFLAGS $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects ${wl}-h $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $CC -G -nostdlib ${wl}-M $wl$lib.exp -o $lib $compiler_flags $predep_objects $libobjs $deplibs $postdep_objects~$rm $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd="$CC -G $CFLAGS -v conftest.$objext 2>&1 | grep \"\-L\"" + fi + + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $wl$libdir' + fi + ;; + esac + ;; + sysv5OpenUNIX8* | sysv5UnixWare7* | sysv5uw[[78]]* | unixware7*) + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + ;; + tandem*) + case $cc_basename in + NCC) + # NonStop-UX NCC 3.20 + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + vxworks*) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; +esac +AC_MSG_RESULT([$_LT_AC_TAGVAR(ld_shlibs, $1)]) +test "$_LT_AC_TAGVAR(ld_shlibs, $1)" = no && can_build_shared=no + +_LT_AC_TAGVAR(GCC, $1)="$GXX" +_LT_AC_TAGVAR(LD, $1)="$LD" + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +AC_LIBTOOL_POSTDEP_PREDEP($1) +AC_LIBTOOL_PROG_COMPILER_PIC($1) +AC_LIBTOOL_PROG_CC_C_O($1) +AC_LIBTOOL_SYS_HARD_LINK_LOCKS($1) +AC_LIBTOOL_PROG_LD_SHLIBS($1) +AC_LIBTOOL_SYS_DYNAMIC_LINKER($1) +AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH($1) +AC_LIBTOOL_SYS_LIB_STRIP +AC_LIBTOOL_DLOPEN_SELF($1) + +AC_LIBTOOL_CONFIG($1) + +AC_LANG_POP +CC=$lt_save_CC +LDCXX=$LD +LD=$lt_save_LD +GCC=$lt_save_GCC +with_gnu_ldcxx=$with_gnu_ld +with_gnu_ld=$lt_save_with_gnu_ld +lt_cv_path_LDCXX=$lt_cv_path_LD +lt_cv_path_LD=$lt_save_path_LD +lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld +lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld +])# AC_LIBTOOL_LANG_CXX_CONFIG + +# AC_LIBTOOL_POSTDEP_PREDEP([TAGNAME]) +# ------------------------ +# Figure out "hidden" library dependencies from verbose +# compiler output when linking a shared library. +# Parse the compiler output and extract the necessary +# objects, libraries and library flags. +AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP],[ +dnl we can't use the lt_simple_compile_test_code here, +dnl because it contains code intended for an executable, +dnl not a library. It's possible we should let each +dnl tag define a new lt_????_link_test_code variable, +dnl but it's only used here... +ifelse([$1],[],[cat > conftest.$ac_ext < conftest.$ac_ext < conftest.$ac_ext < conftest.$ac_ext <> "$cfgfile" +ifelse([$1], [], +[#! $SHELL + +# `$echo "$cfgfile" | sed 's%^.*/%%'` - Provide generalized library-building support services. +# Generated automatically by $PROGRAM (GNU $PACKAGE $VERSION$TIMESTAMP) +# NOTE: Changes made to this file will be lost: look at ltmain.sh. +# +# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001 +# Free Software Foundation, Inc. +# +# This file is part of GNU Libtool: +# Originally by Gordon Matzigkeit , 1996 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# A sed program that does not truncate output. +SED=$lt_SED + +# Sed that helps us avoid accidentally triggering echo(1) options like -n. +Xsed="$SED -e s/^X//" + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +if test "X\${CDPATH+set}" = Xset; then CDPATH=:; export CDPATH; fi + +# The names of the tagged configurations supported by this script. +available_tags= + +# ### BEGIN LIBTOOL CONFIG], +[# ### BEGIN LIBTOOL TAG CONFIG: $tagname]) + +# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`: + +# Shell to use when invoking shell scripts. +SHELL=$lt_SHELL + +# Whether or not to build shared libraries. +build_libtool_libs=$enable_shared + +# Whether or not to build static libraries. +build_old_libs=$enable_static + +# Whether or not to add -lc for building shared libraries. +build_libtool_need_lc=$_LT_AC_TAGVAR(archive_cmds_need_lc, $1) + +# Whether or not to disallow shared libs when runtime libs are static +allow_libtool_libs_with_static_runtimes=$_LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1) + +# Whether or not to optimize for fast installation. +fast_install=$enable_fast_install + +# The host system. +host_alias=$host_alias +host=$host + +# An echo program that does not interpret backslashes. +echo=$lt_echo + +# The archiver. +AR=$lt_AR +AR_FLAGS=$lt_AR_FLAGS + +# A C compiler. +LTCC=$lt_LTCC + +# A language-specific compiler. +CC=$lt_[]_LT_AC_TAGVAR(compiler, $1) + +# Is the compiler the GNU C compiler? +with_gcc=$_LT_AC_TAGVAR(GCC, $1) + +# An ERE matcher. +EGREP=$lt_EGREP + +# The linker used to build libraries. +LD=$lt_[]_LT_AC_TAGVAR(LD, $1) + +# Whether we need hard or soft links. +LN_S=$lt_LN_S + +# A BSD-compatible nm program. +NM=$lt_NM + +# A symbol stripping program +STRIP=$STRIP + +# Used to examine libraries when file_magic_cmd begins "file" +MAGIC_CMD=$MAGIC_CMD + +# Used on cygwin: DLL creation program. +DLLTOOL="$DLLTOOL" + +# Used on cygwin: object dumper. +OBJDUMP="$OBJDUMP" + +# Used on cygwin: assembler. +AS="$AS" + +# The name of the directory that contains temporary libtool files. +objdir=$objdir + +# How to create reloadable object files. +reload_flag=$lt_reload_flag +reload_cmds=$lt_reload_cmds + +# How to pass a linker flag through the compiler. +wl=$lt_[]_LT_AC_TAGVAR(lt_prog_compiler_wl, $1) + +# Object file suffix (normally "o"). +objext="$ac_objext" + +# Old archive suffix (normally "a"). +libext="$libext" + +# Shared library suffix (normally ".so"). +shrext='$shrext' + +# Executable file suffix (normally ""). +exeext="$exeext" + +# Additional compiler flags for building library objects. +pic_flag=$lt_[]_LT_AC_TAGVAR(lt_prog_compiler_pic, $1) +pic_mode=$pic_mode + +# What is the maximum length of a command? +max_cmd_len=$lt_cv_sys_max_cmd_len + +# Does compiler simultaneously support -c and -o options? +compiler_c_o=$lt_[]_LT_AC_TAGVAR(lt_cv_prog_compiler_c_o, $1) + +# Must we lock files when doing compilation ? +need_locks=$lt_need_locks + +# Do we need the lib prefix for modules? +need_lib_prefix=$need_lib_prefix + +# Do we need a version for libraries? +need_version=$need_version + +# Whether dlopen is supported. +dlopen_support=$enable_dlopen + +# Whether dlopen of programs is supported. +dlopen_self=$enable_dlopen_self + +# Whether dlopen of statically linked programs is supported. +dlopen_self_static=$enable_dlopen_self_static + +# Compiler flag to prevent dynamic linking. +link_static_flag=$lt_[]_LT_AC_TAGVAR(lt_prog_compiler_static, $1) + +# Compiler flag to turn off builtin functions. +no_builtin_flag=$lt_[]_LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) + +# Compiler flag to allow reflexive dlopens. +export_dynamic_flag_spec=$lt_[]_LT_AC_TAGVAR(export_dynamic_flag_spec, $1) + +# Compiler flag to generate shared objects directly from archives. +whole_archive_flag_spec=$lt_[]_LT_AC_TAGVAR(whole_archive_flag_spec, $1) + +# Compiler flag to generate thread-safe objects. +thread_safe_flag_spec=$lt_[]_LT_AC_TAGVAR(thread_safe_flag_spec, $1) + +# Library versioning type. +version_type=$version_type + +# Format of library name prefix. +libname_spec=$lt_libname_spec + +# List of archive names. First name is the real one, the rest are links. +# The last name is the one that the linker finds with -lNAME. +library_names_spec=$lt_library_names_spec + +# The coded name of the library, if different from the real name. +soname_spec=$lt_soname_spec + +# Commands used to build and install an old-style archive. +RANLIB=$lt_RANLIB +old_archive_cmds=$lt_[]_LT_AC_TAGVAR(old_archive_cmds, $1) +old_postinstall_cmds=$lt_old_postinstall_cmds +old_postuninstall_cmds=$lt_old_postuninstall_cmds + +# Create an old-style archive from a shared archive. +old_archive_from_new_cmds=$lt_[]_LT_AC_TAGVAR(old_archive_from_new_cmds, $1) + +# Create a temporary old-style archive to link instead of a shared archive. +old_archive_from_expsyms_cmds=$lt_[]_LT_AC_TAGVAR(old_archive_from_expsyms_cmds, $1) + +# Commands used to build and install a shared archive. +archive_cmds=$lt_[]_LT_AC_TAGVAR(archive_cmds, $1) +archive_expsym_cmds=$lt_[]_LT_AC_TAGVAR(archive_expsym_cmds, $1) +postinstall_cmds=$lt_postinstall_cmds +postuninstall_cmds=$lt_postuninstall_cmds + +# Commands used to build a loadable module (assumed same as above if empty) +module_cmds=$lt_[]_LT_AC_TAGVAR(module_cmds, $1) +module_expsym_cmds=$lt_[]_LT_AC_TAGVAR(module_expsym_cmds, $1) + +# Commands to strip libraries. +old_striplib=$lt_old_striplib +striplib=$lt_striplib + +# Dependencies to place before the objects being linked to create a +# shared library. +predep_objects=$lt_[]_LT_AC_TAGVAR(predep_objects, $1) + +# Dependencies to place after the objects being linked to create a +# shared library. +postdep_objects=$lt_[]_LT_AC_TAGVAR(postdep_objects, $1) + +# Dependencies to place before the objects being linked to create a +# shared library. +predeps=$lt_[]_LT_AC_TAGVAR(predeps, $1) + +# Dependencies to place after the objects being linked to create a +# shared library. +postdeps=$lt_[]_LT_AC_TAGVAR(postdeps, $1) + +# The library search path used internally by the compiler when linking +# a shared library. +compiler_lib_search_path=$lt_[]_LT_AC_TAGVAR(compiler_lib_search_path, $1) + +# Method to check whether dependent libraries are shared objects. +deplibs_check_method=$lt_deplibs_check_method + +# Command to use when deplibs_check_method == file_magic. +file_magic_cmd=$lt_file_magic_cmd + +# Flag that allows shared libraries with undefined symbols to be built. +allow_undefined_flag=$lt_[]_LT_AC_TAGVAR(allow_undefined_flag, $1) + +# Flag that forces no undefined symbols. +no_undefined_flag=$lt_[]_LT_AC_TAGVAR(no_undefined_flag, $1) + +# Commands used to finish a libtool library installation in a directory. +finish_cmds=$lt_finish_cmds + +# Same as above, but a single script fragment to be evaled but not shown. +finish_eval=$lt_finish_eval + +# Take the output of nm and produce a listing of raw symbols and C names. +global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe + +# Transform the output of nm in a proper C declaration +global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl + +# Transform the output of nm in a C name address pair +global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address + +# This is the shared library runtime path variable. +runpath_var=$runpath_var + +# This is the shared library path variable. +shlibpath_var=$shlibpath_var + +# Is shlibpath searched before the hard-coded library search path? +shlibpath_overrides_runpath=$shlibpath_overrides_runpath + +# How to hardcode a shared library path into an executable. +hardcode_action=$_LT_AC_TAGVAR(hardcode_action, $1) + +# Whether we should hardcode library paths into libraries. +hardcode_into_libs=$hardcode_into_libs + +# Flag to hardcode \$libdir into a binary during linking. +# This must work even if \$libdir does not exist. +hardcode_libdir_flag_spec=$lt_[]_LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1) + +# If ld is used when linking, flag to hardcode \$libdir into +# a binary during linking. This must work even if \$libdir does +# not exist. +hardcode_libdir_flag_spec_ld=$lt_[]_LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1) + +# Whether we need a single -rpath flag with a separated argument. +hardcode_libdir_separator=$lt_[]_LT_AC_TAGVAR(hardcode_libdir_separator, $1) + +# Set to yes if using DIR/libNAME${shared_ext} during linking hardcodes DIR into the +# resulting binary. +hardcode_direct=$_LT_AC_TAGVAR(hardcode_direct, $1) + +# Set to yes if using the -LDIR flag during linking hardcodes DIR into the +# resulting binary. +hardcode_minus_L=$_LT_AC_TAGVAR(hardcode_minus_L, $1) + +# Set to yes if using SHLIBPATH_VAR=DIR during linking hardcodes DIR into +# the resulting binary. +hardcode_shlibpath_var=$_LT_AC_TAGVAR(hardcode_shlibpath_var, $1) + +# Set to yes if building a shared library automatically hardcodes DIR into the library +# and all subsequent libraries and executables linked against it. +hardcode_automatic=$_LT_AC_TAGVAR(hardcode_automatic, $1) + +# Variables whose values should be saved in libtool wrapper scripts and +# restored at relink time. +variables_saved_for_relink="$variables_saved_for_relink" + +# Whether libtool must link a program against all its dependency libraries. +link_all_deplibs=$_LT_AC_TAGVAR(link_all_deplibs, $1) + +# Compile-time system search path for libraries +sys_lib_search_path_spec=$lt_sys_lib_search_path_spec + +# Run-time system search path for libraries +sys_lib_dlsearch_path_spec=$lt_sys_lib_dlsearch_path_spec + +# Fix the shell variable \$srcfile for the compiler. +fix_srcfile_path="$_LT_AC_TAGVAR(fix_srcfile_path, $1)" + +# Set to yes if exported symbols are required. +always_export_symbols=$_LT_AC_TAGVAR(always_export_symbols, $1) + +# The commands to list exported symbols. +export_symbols_cmds=$lt_[]_LT_AC_TAGVAR(export_symbols_cmds, $1) + +# The commands to extract the exported symbol list from a shared archive. +extract_expsyms_cmds=$lt_extract_expsyms_cmds + +# Symbols that should not be listed in the preloaded symbols. +exclude_expsyms=$lt_[]_LT_AC_TAGVAR(exclude_expsyms, $1) + +# Symbols that must always be exported. +include_expsyms=$lt_[]_LT_AC_TAGVAR(include_expsyms, $1) + +ifelse([$1],[], +[# ### END LIBTOOL CONFIG], +[# ### END LIBTOOL TAG CONFIG: $tagname]) + +__EOF__ + +ifelse([$1],[], [ + case $host_os in + aix3*) + cat <<\EOF >> "$cfgfile" + +# AIX sometimes has problems with the GCC collect2 program. For some +# reason, if we set the COLLECT_NAMES environment variable, the problems +# vanish in a puff of smoke. +if test "X${COLLECT_NAMES+set}" != Xset; then + COLLECT_NAMES= + export COLLECT_NAMES +fi +EOF + ;; + esac + + # We use sed instead of cat because bash on DJGPP gets confused if + # if finds mixed CR/LF and LF-only lines. Since sed operates in + # text mode, it properly converts lines to CR/LF. This bash problem + # is reportedly fixed, but why not run on old versions too? + sed '$q' "$ltmain" >> "$cfgfile" || (rm -f "$cfgfile"; exit 1) + + mv -f "$cfgfile" "$ofile" || \ + (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") + chmod +x "$ofile" +]) +else + # If there is no Makefile yet, we rely on a make rule to execute + # `config.status --recheck' to rerun these tests and create the + # libtool script then. + test -f Makefile && make "$ltmain" +fi +])# AC_LIBTOOL_CONFIG + + +# AC_LIBTOOL_PROG_COMPILER_NO_RTTI([TAGNAME]) +# ------------------------------------------- +AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], +[AC_REQUIRE([_LT_AC_SYS_COMPILER])dnl + +_LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= + +if test "$GCC" = yes; then + _LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' + + AC_LIBTOOL_COMPILER_OPTION([if $compiler supports -fno-rtti -fno-exceptions], + lt_cv_prog_compiler_rtti_exceptions, + [-fno-rtti -fno-exceptions], [], + [_LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)="$_LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) -fno-rtti -fno-exceptions"]) +fi +])# AC_LIBTOOL_PROG_COMPILER_NO_RTTI + + +# AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE +# --------------------------------- +AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], +[AC_REQUIRE([AC_CANONICAL_HOST]) +AC_REQUIRE([AC_PROG_NM]) +AC_REQUIRE([AC_OBJEXT]) +# Check for command to grab the raw symbol name followed by C symbol from nm. +AC_MSG_CHECKING([command to parse $NM output from $compiler object]) +AC_CACHE_VAL([lt_cv_sys_global_symbol_pipe], +[ +# These are sane defaults that work on at least a few old systems. +# [They come from Ultrix. What could be older than Ultrix?!! ;)] + +# Character class describing NM global symbol codes. +symcode='[[BCDEGRST]]' + +# Regexp to match symbols that can be accessed directly from C. +sympat='\([[_A-Za-z]][[_A-Za-z0-9]]*\)' + +# Transform the above into a raw symbol and a C symbol. +symxfrm='\1 \2\3 \3' + +# Transform an extracted symbol line into a proper C declaration +lt_cv_sys_global_symbol_to_cdecl="sed -n -e 's/^. .* \(.*\)$/extern int \1;/p'" + +# Transform an extracted symbol line into symbol name and symbol address +lt_cv_sys_global_symbol_to_c_name_address="sed -n -e 's/^: \([[^ ]]*\) $/ {\\\"\1\\\", (lt_ptr) 0},/p' -e 's/^$symcode \([[^ ]]*\) \([[^ ]]*\)$/ {\"\2\", (lt_ptr) \&\2},/p'" + +# Define system-specific variables. +case $host_os in +aix*) + symcode='[[BCDT]]' + ;; +cygwin* | mingw* | pw32*) + symcode='[[ABCDGISTW]]' + ;; +hpux*) # Its linker distinguishes data from code symbols + if test "$host_cpu" = ia64; then + symcode='[[ABCDEGRST]]' + fi + lt_cv_sys_global_symbol_to_cdecl="sed -n -e 's/^T .* \(.*\)$/extern int \1();/p' -e 's/^$symcode* .* \(.*\)$/extern char \1;/p'" + lt_cv_sys_global_symbol_to_c_name_address="sed -n -e 's/^: \([[^ ]]*\) $/ {\\\"\1\\\", (lt_ptr) 0},/p' -e 's/^$symcode* \([[^ ]]*\) \([[^ ]]*\)$/ {\"\2\", (lt_ptr) \&\2},/p'" + ;; +irix* | nonstopux*) + symcode='[[BCDEGRST]]' + ;; +osf*) + symcode='[[BCDEGQRST]]' + ;; +solaris* | sysv5*) + symcode='[[BDT]]' + ;; +sysv4) + symcode='[[DFNSTU]]' + ;; +esac + +# Handle CRLF in mingw tool chain +opt_cr= +case $build_os in +mingw*) + opt_cr=`echo 'x\{0,1\}' | tr x '\015'` # option cr in regexp + ;; +esac + +# If we're using GNU nm, then use its standard symbol codes. +case `$NM -V 2>&1` in +*GNU* | *'with BFD'*) + symcode='[[ABCDGISTW]]' ;; +esac + +# Try without a prefix undercore, then with it. +for ac_symprfx in "" "_"; do + + # Write the raw and C identifiers. + lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[[ ]]\($symcode$symcode*\)[[ ]][[ ]]*\($ac_symprfx\)$sympat$opt_cr$/$symxfrm/p'" + + # Check to see that the pipe works correctly. + pipe_works=no + + rm -f conftest* + cat > conftest.$ac_ext < $nlist) && test -s "$nlist"; then + # Try sorting and uniquifying the output. + if sort "$nlist" | uniq > "$nlist"T; then + mv -f "$nlist"T "$nlist" + else + rm -f "$nlist"T + fi + + # Make sure that we snagged all the symbols we need. + if grep ' nm_test_var$' "$nlist" >/dev/null; then + if grep ' nm_test_func$' "$nlist" >/dev/null; then + cat < conftest.$ac_ext +#ifdef __cplusplus +extern "C" { +#endif + +EOF + # Now generate the symbol file. + eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | grep -v main >> conftest.$ac_ext' + + cat <> conftest.$ac_ext +#if defined (__STDC__) && __STDC__ +# define lt_ptr_t void * +#else +# define lt_ptr_t char * +# define const +#endif + +/* The mapping between symbol names and symbols. */ +const struct { + const char *name; + lt_ptr_t address; +} +lt_preloaded_symbols[[]] = +{ +EOF + $SED "s/^$symcode$symcode* \(.*\) \(.*\)$/ {\"\2\", (lt_ptr_t) \&\2},/" < "$nlist" | grep -v main >> conftest.$ac_ext + cat <<\EOF >> conftest.$ac_ext + {0, (lt_ptr_t) 0} +}; + +#ifdef __cplusplus +} +#endif +EOF + # Now try linking the two files. + mv conftest.$ac_objext conftstm.$ac_objext + lt_save_LIBS="$LIBS" + lt_save_CFLAGS="$CFLAGS" + LIBS="conftstm.$ac_objext" + CFLAGS="$CFLAGS$_LT_AC_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)" + if AC_TRY_EVAL(ac_link) && test -s conftest${ac_exeext}; then + pipe_works=yes + fi + LIBS="$lt_save_LIBS" + CFLAGS="$lt_save_CFLAGS" + else + echo "cannot find nm_test_func in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot find nm_test_var in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot run $lt_cv_sys_global_symbol_pipe" >&AS_MESSAGE_LOG_FD + fi + else + echo "$progname: failed program was:" >&AS_MESSAGE_LOG_FD + cat conftest.$ac_ext >&5 + fi + rm -f conftest* conftst* + + # Do not use the global_symbol_pipe unless it works. + if test "$pipe_works" = yes; then + break + else + lt_cv_sys_global_symbol_pipe= + fi +done +]) +if test -z "$lt_cv_sys_global_symbol_pipe"; then + lt_cv_sys_global_symbol_to_cdecl= +fi +if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then + AC_MSG_RESULT(failed) +else + AC_MSG_RESULT(ok) +fi +]) # AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE + + +# AC_LIBTOOL_PROG_COMPILER_PIC([TAGNAME]) +# --------------------------------------- +AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC], +[_LT_AC_TAGVAR(lt_prog_compiler_wl, $1)= +_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)= +_LT_AC_TAGVAR(lt_prog_compiler_static, $1)= + +AC_MSG_CHECKING([for $compiler option to produce PIC]) + ifelse([$1],[CXX],[ + # C++ specific cases for pic, static, wl, etc. + if test "$GXX" = yes; then + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + ;; + amigaos*) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the `-m68020' flag to GCC prevents building anything better, + # like `-m68040'. + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + beos* | cygwin* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + mingw* | os2* | pw32*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT' + ;; + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + *djgpp*) + # DJGPP does not support shared libraries at all + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + sysv4*MP*) + if test -d /usr/nec; then + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + hpux*) + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case "$host_cpu" in + hppa*64*|ia64*) + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + else + case $host_os in + aix4* | aix5*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + chorus*) + case $cc_basename in + cxch68) + # Green Hills C++ Compiler + # _LT_AC_TAGVAR(lt_prog_compiler_static, $1)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" + ;; + esac + ;; + dgux*) + case $cc_basename in + ec++) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + ghcx) + # Green Hills C++ Compiler + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + freebsd*) + # FreeBSD uses GNU C++ + ;; + hpux9* | hpux10* | hpux11*) + case $cc_basename in + CC) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)="${ac_cv_prog_cc_wl}-a ${ac_cv_prog_cc_wl}archive" + if test "$host_cpu" != ia64; then + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + fi + ;; + aCC) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)="${ac_cv_prog_cc_wl}-a ${ac_cv_prog_cc_wl}archive" + case "$host_cpu" in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + ;; + *) + ;; + esac + ;; + irix5* | irix6* | nonstopux*) + case $cc_basename in + CC) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + # CC pic flag -KPIC is the default. + ;; + *) + ;; + esac + ;; + linux*) + case $cc_basename in + KCC) + # KAI C++ Compiler + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + icpc) + # Intel C++ + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + cxx) + # Compaq C++ + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + *) + ;; + esac + ;; + lynxos*) + ;; + m88k*) + ;; + mvs*) + case $cc_basename in + cxx) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-W c,exportall' + ;; + *) + ;; + esac + ;; + netbsd*) + ;; + osf3* | osf4* | osf5*) + case $cc_basename in + KCC) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + ;; + RCC) + # Rational C++ 2.4.1 + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + cxx) + # Digital/Compaq C++ + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + *) + ;; + esac + ;; + psos*) + ;; + sco*) + case $cc_basename in + CC) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + *) + ;; + esac + ;; + solaris*) + case $cc_basename in + CC) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + gcx) + # Green Hills C++ Compiler + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + ;; + *) + ;; + esac + ;; + sunos4*) + case $cc_basename in + CC) + # Sun C++ 4.x + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + lcc) + # Lucid + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + tandem*) + case $cc_basename in + NCC) + # NonStop-UX NCC 3.20 + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + *) + ;; + esac + ;; + unixware*) + ;; + vxworks*) + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +], +[ + if test "$GCC" = yes; then + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + ;; + + amigaos*) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the `-m68020' flag to GCC prevents building anything better, + # like `-m68040'. + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + + beos* | cygwin* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + + mingw* | pw32* | os2*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT' + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + + msdosdjgpp*) + # Just because we use GCC doesn't mean we suddenly get shared libraries + # on systems that don't support them. + _LT_AC_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + enable_shared=no + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + + hpux*) + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case "$host_cpu" in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + else + # PORTME Check for flag to pass linker flags through the system compiler. + case $host_os in + aix*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + if test "$host_cpu" = ia64; then + # AIX 5 now supports IA64 processor + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + + mingw* | pw32* | os2*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT' + ;; + + hpux9* | hpux10* | hpux11*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case "$host_cpu" in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + # Is there a better lt_prog_compiler_static that works with the bundled CC? + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='${wl}-a ${wl}archive' + ;; + + irix5* | irix6* | nonstopux*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC (with -KPIC) is the default. + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + newsos6) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + linux*) + case $CC in + icc* | ecc*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + ccc*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All Alpha code is PIC. + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + esac + ;; + + osf3* | osf4* | osf5*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All OSF/1 code is PIC. + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + sco3.2v5*) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-Kpic' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-dn' + ;; + + solaris*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sunos4*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + _LT_AC_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4*MP*) + if test -d /usr/nec ;then + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-Kconform_pic' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + ;; + + uts4*) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_AC_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + *) + _LT_AC_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +]) +AC_MSG_RESULT([$_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)]) + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)"; then + AC_LIBTOOL_COMPILER_OPTION([if $compiler PIC flag $_LT_AC_TAGVAR(lt_prog_compiler_pic, $1) works], + _LT_AC_TAGVAR(lt_prog_compiler_pic_works, $1), + [$_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)ifelse([$1],[],[ -DPIC],[ifelse([$1],[CXX],[ -DPIC],[])])], [], + [case $_LT_AC_TAGVAR(lt_prog_compiler_pic, $1) in + "" | " "*) ;; + *) _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)=" $_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)" ;; + esac], + [_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_AC_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no]) +fi +case "$host_os" in + # For platforms which do not support PIC, -DPIC is meaningless: + *djgpp*) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + *) + _LT_AC_TAGVAR(lt_prog_compiler_pic, $1)="$_LT_AC_TAGVAR(lt_prog_compiler_pic, $1)ifelse([$1],[],[ -DPIC],[ifelse([$1],[CXX],[ -DPIC],[])])" + ;; +esac +]) + + +# AC_LIBTOOL_PROG_LD_SHLIBS([TAGNAME]) +# ------------------------------------ +# See if the linker supports building shared libraries. +AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS], +[AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) +ifelse([$1],[CXX],[ + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + case $host_os in + aix4* | aix5*) + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to AIX nm, but means don't demangle with GNU nm + if $NM -V 2>&1 | grep 'GNU' > /dev/null; then + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\[$]2 == "T") || (\[$]2 == "D") || (\[$]2 == "B")) && ([substr](\[$]3,1,1) != ".")) { print \[$]3 } }'\'' | sort -u > $export_symbols' + else + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\[$]2 == "T") || (\[$]2 == "D") || (\[$]2 == "B")) && ([substr](\[$]3,1,1) != ".")) { print \[$]3 } }'\'' | sort -u > $export_symbols' + fi + ;; + pw32*) + _LT_AC_TAGVAR(export_symbols_cmds, $1)="$ltdll_cmds" + ;; + cygwin* | mingw*) + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGS]] /s/.* \([[^ ]]*\)/\1 DATA/'\'' | $SED -e '\''/^[[AITW]] /s/.* //'\'' | sort | uniq > $export_symbols' + ;; + *) + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + ;; + esac +],[ + runpath_var= + _LT_AC_TAGVAR(allow_undefined_flag, $1)= + _LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1)=no + _LT_AC_TAGVAR(archive_cmds, $1)= + _LT_AC_TAGVAR(archive_expsym_cmds, $1)= + _LT_AC_TAGVAR(old_archive_From_new_cmds, $1)= + _LT_AC_TAGVAR(old_archive_from_expsyms_cmds, $1)= + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)= + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)= + _LT_AC_TAGVAR(thread_safe_flag_spec, $1)= + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1)= + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)= + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_minus_L, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_AC_TAGVAR(link_all_deplibs, $1)=unknown + _LT_AC_TAGVAR(hardcode_automatic, $1)=no + _LT_AC_TAGVAR(module_cmds, $1)= + _LT_AC_TAGVAR(module_expsym_cmds, $1)= + _LT_AC_TAGVAR(always_export_symbols, $1)=no + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + # include_expsyms should be a list of space-separated symbols to be *always* + # included in the symbol list + _LT_AC_TAGVAR(include_expsyms, $1)= + # exclude_expsyms can be an extended regexp of symbols to exclude + # it will be wrapped by ` (' and `)$', so one must not match beginning or + # end of line. Example: `a|bc|.*d.*' will exclude the symbols `a' and `bc', + # as well as any symbol that contains `d'. + _LT_AC_TAGVAR(exclude_expsyms, $1)="_GLOBAL_OFFSET_TABLE_" + # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out + # platforms (ab)use it in PIC code, but their linkers get confused if + # the symbol is explicitly referenced. Since portable code cannot + # rely on this symbol name, it's probably fine to never include it in + # preloaded symbol tables. + extract_expsyms_cmds= + + case $host_os in + cygwin* | mingw* | pw32*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test "$GCC" != yes; then + with_gnu_ld=no + fi + ;; + openbsd*) + with_gnu_ld=no + ;; + esac + + _LT_AC_TAGVAR(ld_shlibs, $1)=yes + if test "$with_gnu_ld" = yes; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='${wl}' + + # See if GNU ld supports shared libraries. + case $host_os in + aix3* | aix4* | aix5*) + # On AIX/PPC, the GNU linker is very broken + if test "$host_cpu" != ia64; then + _LT_AC_TAGVAR(ld_shlibs, $1)=no + cat <&2 + +*** Warning: the GNU linker, at least up to release 2.9.1, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to modify your PATH +*** so that a non-GNU linker is found, and then restart. + +EOF + fi + ;; + + amigaos*) + _LT_AC_TAGVAR(archive_cmds, $1)='$rm $output_objdir/a2ixlibrary.data~$echo "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$echo "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$echo "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$echo "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + + # Samuel A. Falvo II reports + # that the semantics of dynamic libraries on AmigaOS, at least up + # to version 4, is to share data among multiple programs linked + # with the same dynamic library. Since this doesn't match the + # behavior of shared libraries on other platforms, we can't use + # them. + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + + beos*) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -nostart $compiler_flags $libobjs $deplibs ${wl}-soname $wl$soname -o $lib' + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + cygwin* | mingw* | pw32*) + # _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(allow_undefined_flag, $1)=no + _LT_AC_TAGVAR(always_export_symbols, $1)=no + _LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGS]] /s/.* \([[^ ]]*\)/\1 DATA/'\'' | $SED -e '\''/^[[AITW]] /s/.* //'\'' | sort | uniq > $export_symbols' + + if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $compiler_flags $libobjs $deplibs -o $output_objdir/$soname ${wl}--image-base=0x10000000 ${wl}--out-implib,$lib' + # If the export-symbols file already is a .def file (1st line + # is EXPORTS), use it as is; otherwise, prepend... + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared $output_objdir/$soname.def $compiler_flags $libobjs $deplibs -o $output_objdir/$soname ${wl}--image-base=0x10000000 ${wl}--out-implib,$lib' + else + ld_shlibs=no + fi + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' + wlarc= + else + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $compiler_flags $libobjs $deplibs ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $compiler_flags $libobjs $deplibs ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + fi + ;; + + solaris* | sysv5*) + if $LD -v 2>&1 | grep 'BFD 2\.8' > /dev/null; then + _LT_AC_TAGVAR(ld_shlibs, $1)=no + cat <&2 + +*** Warning: The releases 2.8.* of the GNU linker cannot reliably +*** create shared libraries on Solaris systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.9.1 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +EOF + elif $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $compiler_flags $libobjs $deplibs ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $compiler_flags $libobjs $deplibs ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + sunos4*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' + wlarc= + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $compiler_flags $libobjs $deplibs ${wl}-soname $wl$soname -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $compiler_flags $libobjs $deplibs ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + + if test "$_LT_AC_TAGVAR(ld_shlibs, $1)" = yes; then + runpath_var=LD_RUN_PATH + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}--rpath ${wl}$libdir' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic' + # ancient GNU ld didn't support --whole-archive et. al. + if $LD --help 2>&1 | grep 'no-whole-archive' > /dev/null; then + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive' + else + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)= + fi + fi + else + # PORTME fill in a description of your system's linker (not GNU ld) + case $host_os in + aix3*) + _LT_AC_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_AC_TAGVAR(always_export_symbols, $1)=yes + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + if test "$GCC" = yes && test -z "$link_static_flag"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + _LT_AC_TAGVAR(hardcode_direct, $1)=unsupported + fi + ;; + + aix4* | aix5*) + if test "$host_cpu" = ia64; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag="" + else + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to AIX nm, but means don't demangle with GNU nm + if $NM -V 2>&1 | grep 'GNU' > /dev/null; then + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\[$]2 == "T") || (\[$]2 == "D") || (\[$]2 == "B")) && ([substr](\[$]3,1,1) != ".")) { print \[$]3 } }'\'' | sort -u > $export_symbols' + else + _LT_AC_TAGVAR(export_symbols_cmds, $1)='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\[$]2 == "T") || (\[$]2 == "D") || (\[$]2 == "B")) && ([substr](\[$]3,1,1) != ".")) { print \[$]3 } }'\'' | sort -u > $export_symbols' + fi + + # KDE requires run time linking. Make it the default. + aix_use_runtimelinking=yes + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_AC_TAGVAR(archive_cmds, $1)='' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + + if test "$GCC" = yes; then + case $host_os in aix4.[012]|aix4.[012].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && \ + strings "$collect2name" | grep resolve_lib_name >/dev/null + then + # We have reworked collect2 + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + else + # We have old collect2 + _LT_AC_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)= + fi + esac + shared_flag='-shared' + else + # not using gcc + if test "$host_cpu" = ia64; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test "$aix_use_runtimelinking" = yes; then + shared_flag='-qmkshrobj ${wl}-G' + else + shared_flag='-qmkshrobj' + fi + fi + fi + + # Let the compiler handle the export list. + _LT_AC_TAGVAR(always_export_symbols, $1)=no + if test "$aix_use_runtimelinking" = yes; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-berok' + # Determine the default libpath from the value encoded in an empty executable. + _LT_AC_SYS_LIBPATH_AIX + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath" + _LT_AC_TAGVAR(archive_cmds, $1)="\$CC"' -o $output_objdir/$soname $compiler_flags $libobjs $deplibs `if test "x${allow_undefined_flag}" != "x"; then echo "${wl}${allow_undefined_flag}"; else :; fi` '" $shared_flag" + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="\$CC"' -o $output_objdir/$soname $compiler_flags $libobjs $deplibs `if test "x${allow_undefined_flag}" != "x"; then echo "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag" + else + if test "$host_cpu" = ia64; then + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $libdir:/usr/lib:/lib' + _LT_AC_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $compiler_flags $libobjs $deplibs ${wl}${allow_undefined_flag} '"\${wl}$no_entry_flag \${wl}$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an empty executable. + _LT_AC_SYS_LIBPATH_AIX + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_AC_TAGVAR(no_undefined_flag, $1)=' ${wl}-bernotok' + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-berok' + # -bexpall does not export symbols beginning with underscore (_) + _LT_AC_TAGVAR(always_export_symbols, $1)=yes + # Exported symbols can be pulled into shared objects from archives + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)=' ' + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=yes + # This is similar to how AIX traditionally builds it's shared libraries. + _LT_AC_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $compiler_flags $libobjs $deplibs ${wl}-bE:$export_symbols ${wl}-bnoentry${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname' + fi + fi + ;; + + amigaos*) + _LT_AC_TAGVAR(archive_cmds, $1)='$rm $output_objdir/a2ixlibrary.data~$echo "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$echo "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$echo "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$echo "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + # see comment about different semantics on the GNU ld section + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + + bsdi4*) + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)=-rdynamic + ;; + + cygwin* | mingw* | pw32*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_AC_TAGVAR(allow_undefined_flag, $1)=no + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext=".dll" + # FIXME: Setting linknames here is a bad hack. + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -o $lib $compiler_flags $libobjs `echo "$deplibs" | $SED -e '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + _LT_AC_TAGVAR(old_archive_From_new_cmds, $1)='true' + # FIXME: Should let the user specify the lib program. + _LT_AC_TAGVAR(old_archive_cmds, $1)='lib /OUT:$oldlib$oldobjs$old_deplibs' + fix_srcfile_path='`cygpath -w "$srcfile"`' + _LT_AC_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + darwin* | rhapsody*) + if test "$GXX" = yes ; then + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + case "$host_os" in + rhapsody* | darwin1.[[012]]) + _LT_AC_TAGVAR(allow_undefined_flag, $1)='-Wl,-undefined -Wl,suppress' + ;; + *) # Darwin 1.3 on + if test -z ${MACOSX_DEPLOYMENT_TARGET} ; then + allow_undefined_flag='-Wl,-flat_namespace -Wl,-undefined -Wl,suppress' + else + case ${MACOSX_DEPLOYMENT_TARGET} in + 10.[012]) + allow_undefined_flag='-Wl,-flat_namespace -Wl,-undefined -Wl,suppress' + ;; + 10.*) + allow_undefined_flag='-Wl,-undefined -Wl,dynamic_lookup' + ;; + esac + fi + ;; + esac + lt_int_apple_cc_single_mod=no + output_verbose_link_cmd='echo' + if $CC -dumpspecs 2>&1 | grep 'single_module' >/dev/null ; then + lt_int_apple_cc_single_mod=yes + fi + if test "X$lt_int_apple_cc_single_mod" = Xyes ; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -dynamiclib -single_module $allow_undefined_flag -o $lib $compiler_flags $libobjs $deplibs -install_name $rpath/$soname $verstring' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -r ${wl}-bind_at_load -keep_private_externs -nostdlib -o ${lib}-master.o $libobjs~$CC -dynamiclib $allow_undefined_flag -o $lib ${lib}-master.o $compiler_flags $deplibs -install_name $rpath/$soname $verstring' + fi + _LT_AC_TAGVAR(module_cmds, $1)='$CC ${wl}-bind_at_load $allow_undefined_flag -o $lib -bundle $compiler_flags $libobjs $deplibs' + # Don't fix this by using the ld -exported_symbols_list flag, it doesn't exist in older darwin ld's + if test "X$lt_int_apple_cc_single_mod" = Xyes ; then + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC -dynamiclib -single_module $allow_undefined_flag -o $lib $compiler_flags $libobjs $deplibs -install_name $rpath/$soname $verstring~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + else + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC -r ${wl}-bind_at_load -keep_private_externs -nostdlib -o ${lib}-master.o $libobjs~$CC -dynamiclib $allow_undefined_flag -o $lib ${lib}-master.o $compiler_flags $deplibs -install_name $rpath/$soname $verstring~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + fi + _LT_AC_TAGVAR(module_expsym_cmds, $1)='sed -e "s,#.*,," -e "s,^[ ]*,," -e "s,^\(..*\),_&," < $export_symbols > $output_objdir/${libname}-symbols.expsym~$CC $allow_undefined_flag -o $lib -bundle $compiler_flags $libobjs $deplibs~nmedit -s $output_objdir/${libname}-symbols.expsym ${lib}' + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_automatic, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='-all_load $convenience' + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + else + _LT_AC_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + dgux*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + freebsd1*) + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd*) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -o $lib $compiler_flags $libobjs $deplibs' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + hpux9*) + if test "$GCC" = yes; then + _LT_AC_TAGVAR(archive_cmds, $1)='$rm $output_objdir/$soname~$CC -shared -fPIC ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $compiler_flags $libobjs $deplibs~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$rm $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' + fi + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + ;; + + hpux10* | hpux11*) + if test "$GCC" = yes -a "$with_gnu_ld" = no; then + case "$host_cpu" in + hppa*64*|ia64*) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}+h ${wl}$soname -o $lib $compiler_flags $libobjs $deplibs' + ;; + *) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared -fPIC ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $compiler_flags $libobjs $deplibs' + ;; + esac + else + case "$host_cpu" in + hppa*64*|ia64*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -b +h $soname -o $lib $libobjs $deplibs $linker_flags' + ;; + *) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' + ;; + esac + fi + if test "$with_gnu_ld" = no; then + case "$host_cpu" in + hppa*64*) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1)='+b $libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + ia64*) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_direct, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + ;; + *) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + fi + ;; + + irix5* | irix6* | nonstopux*) + if test "$GCC" = yes; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $compiler_flags $libobjs $deplibs ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -shared $libobjs $deplibs $linker_flags -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec_ld, $1)='-rpath $libdir' + fi + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out + else + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF + fi + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + newsos6) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + openbsd*) + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $compiler_flags $libobjs $deplibs' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E' + else + case $host_os in + openbsd[[01]].* | openbsd2.[[0-7]] | openbsd2.[[0-7]].*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + ;; + *) + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $compiler_flags $libobjs $deplibs' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir' + ;; + esac + fi + ;; + + os2*) + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + _LT_AC_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_AC_TAGVAR(archive_cmds, $1)='$echo "LIBRARY $libname INITINSTANCE" > $output_objdir/$libname.def~$echo "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~$echo DATA >> $output_objdir/$libname.def~$echo " SINGLE NONSHARED" >> $output_objdir/$libname.def~$echo EXPORTS >> $output_objdir/$libname.def~emxexp $libobjs >> $output_objdir/$libname.def~$CC -Zdll -Zcrtdll -o $lib $compiler_flags $libobjs $deplibs$output_objdir/$libname.def' + _LT_AC_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/$libname.a $output_objdir/$libname.def' + ;; + + osf3*) + if test "$GCC" = yes; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $compiler_flags $libobjs $deplibs ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + else + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -shared${allow_undefined_flag} $libobjs $deplibs $linker_flags -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + fi + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + osf4* | osf5*) # as osf3* with the addition of -msym flag + if test "$GCC" = yes; then + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*' + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $compiler_flags $libobjs $deplibs ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir' + else + _LT_AC_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -shared${allow_undefined_flag} $libobjs $deplibs $linker_flags -msym -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; echo "-hidden">> $lib.exp~ + $LD -shared${allow_undefined_flag} -input $lib.exp $linker_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${objdir}/so_locations -o $lib~$rm $lib.exp' + + # Both c and cxx compiler support -rpath directly + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + fi + _LT_AC_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + sco3.2v5*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-Bexport' + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + ;; + + solaris*) + _LT_AC_TAGVAR(no_undefined_flag, $1)=' -z text' + if test "$GCC" = yes; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h ${wl}$soname -o $lib $compiler_flags $libobjs $deplibs' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $CC -shared ${wl}-M ${wl}$lib.exp ${wl}-h ${wl}$soname -o $lib $compiler_flags $libobjs $deplibs~$rm $lib.exp' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$rm $lib.exp' + fi + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) # Supported since Solaris 2.6 (maybe 2.5.1?) + _LT_AC_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' ;; + esac + _LT_AC_TAGVAR(link_all_deplibs, $1)=yes + ;; + + sunos4*) + if test "x$host_vendor" = xsequent; then + # Use $CC to link under sequent, because it throws in some extra .o + # files that make .init and .fini sections work. + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h $soname -o $lib $compiler_flags $libobjs $deplibs' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' + fi + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_minus_L, $1)=yes + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4) + case $host_vendor in + sni) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes # is this really true??? + ;; + siemens) + ## LD is ld it makes a PLAMLIB + ## CC just makes a GrossModule. + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(reload_cmds, $1)='$CC -r -o $output$reload_objs' + _LT_AC_TAGVAR(hardcode_direct, $1)=no + ;; + motorola) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_direct, $1)=no #Motorola manual says yes, but my tests say they lie + ;; + esac + runpath_var='LD_RUN_PATH' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4.3*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_AC_TAGVAR(export_dynamic_flag_spec, $1)='-Bexport' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + _LT_AC_TAGVAR(ld_shlibs, $1)=yes + fi + ;; + + sysv4.2uw2*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_direct, $1)=yes + _LT_AC_TAGVAR(hardcode_minus_L, $1)=no + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + hardcode_runpath_var=yes + runpath_var=LD_RUN_PATH + ;; + + sysv5OpenUNIX8* | sysv5UnixWare7* | sysv5uw[[78]]* | unixware7*) + _LT_AC_TAGVAR(no_undefined_flag, $1)='${wl}-z ${wl}text' + if test "$GCC" = yes; then + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h ${wl}$soname -o $lib $compiler_flags $libobjs $deplibs' + else + _LT_AC_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h ${wl}$soname -o $lib $compiler_flags $libobjs $deplibs' + fi + runpath_var='LD_RUN_PATH' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv5*) + _LT_AC_TAGVAR(no_undefined_flag, $1)=' -z text' + # $CC -shared without GNU ld will not create a library from C++ + # object files and a static libstdc++, better avoid it by now + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(archive_expsym_cmds, $1)='$echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~ + $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$rm $lib.exp' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var='LD_RUN_PATH' + ;; + + uts4*) + _LT_AC_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_AC_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_AC_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + _LT_AC_TAGVAR(ld_shlibs, $1)=no + ;; + esac + fi +]) +AC_MSG_RESULT([$_LT_AC_TAGVAR(ld_shlibs, $1)]) +test "$_LT_AC_TAGVAR(ld_shlibs, $1)" = no && can_build_shared=no + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test "$GCC" = yes; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +# +# Do we need to explicitly link libc? +# +case "x$_LT_AC_TAGVAR(archive_cmds_need_lc, $1)" in +x|xyes) + # Assume -lc should be added + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=yes + + if test "$enable_shared" = yes && test "$GCC" = yes; then + case $_LT_AC_TAGVAR(archive_cmds, $1) in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + AC_MSG_CHECKING([whether -lc should be explicitly linked in]) + $rm conftest* + printf "$lt_simple_compile_test_code" > conftest.$ac_ext + + if AC_TRY_EVAL(ac_compile) 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$_LT_AC_TAGVAR(lt_prog_compiler_wl, $1) + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$_LT_AC_TAGVAR(allow_undefined_flag, $1) + _LT_AC_TAGVAR(allow_undefined_flag, $1)= + if AC_TRY_EVAL(_LT_AC_TAGVAR(archive_cmds, $1) 2\>\&1 \| grep \" -lc \" \>/dev/null 2\>\&1) + then + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=no + else + _LT_AC_TAGVAR(archive_cmds_need_lc, $1)=yes + fi + _LT_AC_TAGVAR(allow_undefined_flag, $1)=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $rm conftest* + AC_MSG_RESULT([$_LT_AC_TAGVAR(archive_cmds_need_lc, $1)]) + ;; + esac + fi + ;; +esac +])# AC_LIBTOOL_PROG_LD_SHLIBS + + +# _LT_AC_FILE_LTDLL_C +# ------------------- +# Be careful that the start marker always follows a newline. +AC_DEFUN([_LT_AC_FILE_LTDLL_C], [ +# /* ltdll.c starts here */ +# #define WIN32_LEAN_AND_MEAN +# #include +# #undef WIN32_LEAN_AND_MEAN +# #include +# +# #ifndef __CYGWIN__ +# # ifdef __CYGWIN32__ +# # define __CYGWIN__ __CYGWIN32__ +# # endif +# #endif +# +# #ifdef __cplusplus +# extern "C" { +# #endif +# BOOL APIENTRY DllMain (HINSTANCE hInst, DWORD reason, LPVOID reserved); +# #ifdef __cplusplus +# } +# #endif +# +# #ifdef __CYGWIN__ +# #include +# DECLARE_CYGWIN_DLL( DllMain ); +# #endif +# HINSTANCE __hDllInstance_base; +# +# BOOL APIENTRY +# DllMain (HINSTANCE hInst, DWORD reason, LPVOID reserved) +# { +# __hDllInstance_base = hInst; +# return TRUE; +# } +# /* ltdll.c ends here */ +])# _LT_AC_FILE_LTDLL_C + + +# _LT_AC_TAGVAR(VARNAME, [TAGNAME]) +# --------------------------------- +AC_DEFUN([_LT_AC_TAGVAR], [ifelse([$2], [], [$1], [$1_$2])]) + + +# old names +AC_DEFUN([AM_PROG_LIBTOOL], [AC_PROG_LIBTOOL]) +AC_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)]) +AC_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)]) +AC_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)]) +AC_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)]) +AC_DEFUN([AM_PROG_LD], [AC_PROG_LD]) +AC_DEFUN([AM_PROG_NM], [AC_PROG_NM]) + +# This is just to silence aclocal about the macro not being used +ifelse([AC_DISABLE_FAST_INSTALL]) + +AC_DEFUN([LT_AC_PROG_GCJ], +[AC_CHECK_TOOL(GCJ, gcj, no) + test "x${GCJFLAGS+set}" = xset || GCJFLAGS="-g -O2" + AC_SUBST(GCJFLAGS) +]) + +AC_DEFUN([LT_AC_PROG_RC], +[AC_CHECK_TOOL(RC, windres, no) +]) + +############################################################ +# NOTE: This macro has been submitted for inclusion into # +# GNU Autoconf as AC_PROG_SED. When it is available in # +# a released version of Autoconf we should remove this # +# macro and use it instead. # +############################################################ +# LT_AC_PROG_SED +# -------------- +# Check for a fully-functional sed program, that truncates +# as few characters as possible. Prefer GNU sed if found. +AC_DEFUN([LT_AC_PROG_SED], +[AC_MSG_CHECKING([for a sed that does not truncate output]) +AC_CACHE_VAL(lt_cv_path_SED, +[# Loop through the user's path and test for sed and gsed. +# Then use that list of sed's as ones to test for truncation. +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for lt_ac_prog in sed gsed; do + for ac_exec_ext in '' $ac_executable_extensions; do + if $as_executable_p "$as_dir/$lt_ac_prog$ac_exec_ext"; then + lt_ac_sed_list="$lt_ac_sed_list $as_dir/$lt_ac_prog$ac_exec_ext" + fi + done + done +done +lt_ac_max=0 +lt_ac_count=0 +# Add /usr/xpg4/bin/sed as it is typically found on Solaris +# along with /bin/sed that truncates output. +for lt_ac_sed in $lt_ac_sed_list /usr/xpg4/bin/sed; do + test ! -f $lt_ac_sed && break + cat /dev/null > conftest.in + lt_ac_count=0 + echo $ECHO_N "0123456789$ECHO_C" >conftest.in + # Check for GNU sed and select it if it is found. + if "$lt_ac_sed" --version 2>&1 < /dev/null | grep 'GNU' > /dev/null; then + lt_cv_path_SED=$lt_ac_sed + break + fi + while true; do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo >>conftest.nl + $lt_ac_sed -e 's/a$//' < conftest.nl >conftest.out || break + cmp -s conftest.out conftest.nl || break + # 10000 chars as input seems more than enough + test $lt_ac_count -gt 10 && break + lt_ac_count=`expr $lt_ac_count + 1` + if test $lt_ac_count -gt $lt_ac_max; then + lt_ac_max=$lt_ac_count + lt_cv_path_SED=$lt_ac_sed + fi + done +done +]) +SED=$lt_cv_path_SED +AC_MSG_RESULT([$SED]) +]) + +dnl PKG_CHECK_MODULES(GSTUFF, gtk+-2.0 >= 1.3 glib = 1.3.4, action-if, action-not) +dnl defines GSTUFF_LIBS, GSTUFF_CFLAGS, see pkg-config man page +dnl also defines GSTUFF_PKG_ERRORS on error +AC_DEFUN([PKG_CHECK_MODULES], [ + succeeded=no + + if test -z "$PKG_CONFIG"; then + AC_PATH_PROG(PKG_CONFIG, pkg-config, no) + fi + + if test "$PKG_CONFIG" = "no" ; then + echo "*** The pkg-config script could not be found. Make sure it is" + echo "*** in your path, or set the PKG_CONFIG environment variable" + echo "*** to the full path to pkg-config." + echo "*** Or see http://www.freedesktop.org/software/pkgconfig to get pkg-config." + else + PKG_CONFIG_MIN_VERSION=0.9.0 + if $PKG_CONFIG --atleast-pkgconfig-version $PKG_CONFIG_MIN_VERSION; then + AC_MSG_CHECKING(for $2) + + if $PKG_CONFIG --exists "$2" ; then + AC_MSG_RESULT(yes) + succeeded=yes + + AC_MSG_CHECKING($1_CFLAGS) + $1_CFLAGS=`$PKG_CONFIG --cflags "$2"` + AC_MSG_RESULT($$1_CFLAGS) + + AC_MSG_CHECKING($1_LIBS) + $1_LIBS=`$PKG_CONFIG --libs "$2"` + AC_MSG_RESULT($$1_LIBS) + else + $1_CFLAGS="" + $1_LIBS="" + ## If we have a custom action on failure, don't print errors, but + ## do set a variable so people can do so. + $1_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "$2"` + ifelse([$4], ,echo $$1_PKG_ERRORS,) + fi + + AC_SUBST($1_CFLAGS) + AC_SUBST($1_LIBS) + else + echo "*** Your version of pkg-config is too old. You need version $PKG_CONFIG_MIN_VERSION or newer." + echo "*** See http://www.freedesktop.org/software/pkgconfig" + fi + fi + + if test $succeeded = yes; then + ifelse([$3], , :, [$3]) + else + ifelse([$4], , AC_MSG_ERROR([Library requirements ($2) not met; consider adjusting the PKG_CONFIG_PATH environment variable if your libraries are in a nonstandard prefix so pkg-config can find them.]), [$4]) + fi +]) + + +dnl lrelease fix see https://bugzilla.novell.com/show_bug.cgi?id=327023 + +# Check for lrelease compress flag +AC_DEFUN([ms_CHECK_LRELEASE], +[ +AC_MSG_CHECKING([LRELEASE OPTIONS]) + +if lrelease -compress /dev/null -qm /dev/null 1>/dev/null 2>&1 +then +AC_MSG_RESULT([compress]) +LRELEASEOPTION="-compress" +else +AC_MSG_RESULT([nocompress]) +LRELEASEOPTION="" +fi + +AC_SUBST([LRELEASEOPTION]) +]) diff --git a/configure.in b/configure.in new file mode 100644 index 0000000..edd4953 --- /dev/null +++ b/configure.in @@ -0,0 +1,346 @@ +AC_INIT(src/phone.h) +AC_CANONICAL_SYSTEM +AM_CONFIG_HEADER(src/twinkle_config.h) + +AC_ARG_ENABLE(qt-check, + AC_HELP_STRING([--disable-qt-check], [do not check Qt installation]), + [ac_cv_qt_check=$enableval], [ac_cv_qt_check=yes]) + +AC_ARG_WITH(kde, + AC_HELP_STRING([--without-kde], [do not compile KDE features]), + [ac_cv_kde=$withval], [ac_cv_kde=yes]) + +AC_ARG_WITH(ilbc, + AC_HELP_STRING([--without-ilbc], [do not compile iLBC]), + [ac_cv_ilbc=$withval], [ac_cv_ilbc=yes]) + +AC_ARG_WITH(speex, + AC_HELP_STRING([--without-speex], [do not compile speex]), + [ac_cv_speex=$withval], [ac_cv_speex=yes]) + +AC_ARG_WITH(zrtp, + AC_HELP_STRING([--without-zrtp], [do not compile zrtp support]), + [ac_cv_zrtp=$withval], [ac_cv_zrtp=yes]) + +AC_ARG_ENABLE(ilbc-cpp, + AC_HELP_STRING([--enable-ilbc-cpp], [your ilbc library is built for C++ instead of C]), + [ac_cv_ilbc_cpp=$enableval], [ac_cv_ilbc_cpp=no]) + +AM_INIT_AUTOMAKE(twinkle,"1.4.2") +AC_DEFINE(VERSION_DATE,["February 25 2009"],[Version release date]) +AC_PROG_CC +AC_PROG_CXX +AC_PROG_CPP +AC_PROG_CXXCPP +AC_PROG_RANLIB +AC_PROG_LEX +AC_PROG_YACC +AC_C_BIGENDIAN +AC_LANG(C++) +AC_FUNC_STRERROR_R + +AC_CHECK_HEADERS([linux/types.h]) +AC_CHECK_HEADERS([linux/errqueue.h],[],[],[ + #if HAVE_LINUX_TYPES_H + #include + #endif]) + +# Check version of the Common C++ library. +# This also sets the cc++2 include directory in CXXFLAGS +OST_CCXX2_VERSION(1.6.0,,exit) + +# Temporarily add some default directories to PKG_CONFIG_PATH such that +# the user will not be burdened with setting PKG_CONFIG_PATH +OLD_PKG_CONFIG_PATH=$PKG_CONFIG_PATH +PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig + +if test "x${prefix}" != "xNONE" +then + PKG_CONFIG_PATH=$PKG_CONFIG_PATH:${prefix}/lib/pkgconfig +fi + +if test -n "$QTDIR" +then + PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$QTDIR/lib/pkgconfig +fi + +export PKG_CONFIG_PATH + +PKG_CHECK_MODULES(CCRTP, libccrtp1 >= 1.6.0) + +PKG_CHECK_MODULES(XML2, libxml-2.0) +# AC_CHECK_HEADER(libxml/tree.h, [], +# [AC_MSG_ERROR([libxml2 header files missing (libxml2-devel package)])]) + +if test "x$ac_cv_qt_check" = "xyes" +then + PKG_CHECK_MODULES(QT, qt-mt >= 3.3.0 qt-mt < 4.0) +fi + +# Restore the original value of PKG_CONFIG_PATH +PKG_CONFIG_PATH=$OLD_PKG_CONFIG_PATH +export PKG_CONFIG_PATH + +# Check if QTDIR variable is set +AC_MSG_CHECKING([value of \$QTDIR]) +if test -n "$QTDIR" +then + AC_MSG_RESULT([$QTDIR]) +else + AC_MSG_RESULT([not set]) + AC_MSG_ERROR([Set \$QTDIR to the Qt directory, eg. /usr/lib/qt3]) +fi + +AC_MSG_CHECKING([for qmake]) +if test -x $QTDIR/bin/qmake +then + AC_MSG_RESULT([yes]) +else + AC_MSG_RESULT([no]) + AC_MSG_ERROR([Cannot find qmake in \$QTDIR/bin. \$QTDIR is incorrect.]) +fi + +# Without this macro, compiling on non-kde systems does not work +AC_PATH_QT + +# Check if lrelease is available +AC_CHECK_PROG(have_lrelease, lrelease, yes, no) +if test "x$have_lrelease" = "xno" +then + AC_MSG_ERROR([lrelease is missing (qt3-devel-tools package)]) +fi + +dnl Create a Qt include project file for platform dependend variables +dnl This file will be included by the project file in the gui directory +dnl Determine include files and dynamic libs. +dnl Creation of qtccxxincl.pro is good to be in AC_CONFIG_COMMANDS +dnl macro so that config.status to be able to create it too. +dnl In this case we benefit from more ac_xxx variables. +dnl Disadvantage is autoconf don't group commands with same tag +dnl and we should use different tags. In result AC_OUTPUT will +dnl show all of them. +QT_INCL_PRO=qtccxxincl.pro +AC_MSG_NOTICE([creating $QT_INCL_PRO (include project file for Qt)]) + +# first set include path to generated files +echo "INCLUDEPATH += `pwd`/src" > $QT_INCL_PRO +# next get includes specified on command line +( +set -- $CPPFLAGS +while test -n "$1"; do + echo $1 + shift +done +) | grep '^-I' | sed -e 's|^-I||' | xargs echo INCLUDEPATH += >> $QT_INCL_PRO + +#echo "INCLUDEPATH += `$CCGNU2_CONFIG --includes`" | sed -e s/-I//g > $QT_INCL_PRO +# libccrtp1(ccrtp) depend from libccgnu2(commoncpp2) and +# should include above flags ! +echo "INCLUDEPATH += `$PKG_CONFIG --cflags-only-I libccrtp1`" | sed -e s/-I//g >> $QT_INCL_PRO +echo "INCLUDEPATH += `$PKG_CONFIG --cflags-only-I libxml-2.0`" | sed -e s/-I//g >> $QT_INCL_PRO + +# get libraries specified on command line +echo $LDFLAGS | xargs echo LIBS += >> $QT_INCL_PRO + +echo "LIBS += `$CCGNU2_CONFIG --stdlibs`" >> $QT_INCL_PRO +echo "LIBS += $CCRTP_LIBS" >> $QT_INCL_PRO +echo "LIBS += $XML2_LIBS" >> $QT_INCL_PRO + +# Check if KDE is available +if test "x$ac_cv_kde" = "xyes" +then + AC_CHECK_PROG(have_kde, kde-config, yes, no) +else + have_kde="no" +fi + +if test "x$have_kde" = "xyes" +then + KDE_SET_PREFIX + AC_PATH_KDE + + AC_CHECK_FILES(${kde_includes}/kapplication.h, + [], + [AC_MSG_ERROR([kde header files missing (kdelibs3-devel package)])]) + AC_DEFINE(HAVE_KDE, 1, [Define to 1 if you have KDE.]) + echo "INCLUDEPATH += ${kde_includes}" >> $QT_INCL_PRO + echo "LIBS += -L${kde_libraries} -lkdecore -lkdeui" >> $QT_INCL_PRO + echo "LIBS += -L${TWINKLE_KDE_PREFIX}/lib -lkabc" >> $QT_INCL_PRO +else + include_x11_FALSE="yes" + include_ARTS_FALSE="yes" + AC_PREFIX_DEFAULT(${prefix:-/usr/local}) +fi + +# Check for libbbind or libresolv. libbind is preferred as libresolv gives +# GLIBC_PRIVATE on Fedora. +AC_CHECK_LIB(bind, main, [ + LIBS="-lbind $LIBS" + echo "LIBS += -lbind" >> $QT_INCL_PRO], + [ + LIBS="-lresolv $LIBS" + echo "LIBS += -lresolv" >> $QT_INCL_PRO]) + +# Check if sndfile library is available +AC_CHECK_HEADER(sndfile.h, [], + [AC_MSG_ERROR([sndfile header files missing (libsndfile-devel package)])]) +AC_CHECK_LIB(sndfile, sf_open, [], + [AC_MSG_ERROR([libsndfile library is missing.])]) + +# Check if magic library is available +AC_CHECK_HEADER(magic.h, [], + [AC_MSG_ERROR([magic.h is missing (file-devel or libmagic-dev package)])]) +AC_CHECK_LIB(magic, magic_open, [], + [AC_MSG_ERROR([libmagic library is missing (file or libmagic package)])]) + +# This check does not work on all platforms +# Check if libgsm is available +# AC_CHECK_LIB(gsm, sf_open, [ +# AC_CHECK_HEADER(gsm.h, [], +# [AC_MSG_ERROR([gsm header files missing (gsm.h)])]) +# AC_DEFINE(HAVE_GSM, 1, [Define to 1 if you have the library.]) +# GSM_LIBS="-lgsm" +# echo "LIBS += -lgsm" >> $QT_INCL_PRO +# have_gsm="yes" ], [ +# have_gsm="no" +# GSM_LIBS="\$(top_builddir)/src/audio/gsm/libgsm.a" +# echo "LIBS += ../audio/gsm/libgsm.a" >> $QT_INCL_PRO ]) +have_gsm="no" +GSM_LIBS="\$(top_builddir)/src/audio/gsm/libgsm.a" +echo "LIBS += ../audio/gsm/libgsm.a" >> $QT_INCL_PRO + +AC_SUBST(GSM_LIBS) + +# Check if ALSA is available +AC_CHECK_LIB(asound, main, [ + AC_CHECK_HEADER(alsa/asoundlib.h, [], + [AC_MSG_ERROR([alsa header files missing (alsa-devel package)])]) + AC_DEFINE(HAVE_LIBASOUND, 1, [Define to 1 if you have the library.]) + LIBS="-lasound $LIBS" + echo "LIBS += -lasound" >> $QT_INCL_PRO + have_libasound="yes" + ], [have_libasound="no"]) + +# Check if SPEEX (libspeex & libspeexdsp) is available +if test "x$ac_cv_speex" = "xyes" +then + AC_CHECK_LIB(speex, main, [ + AC_CHECK_HEADER(speex/speex.h, [], + [AC_MSG_ERROR([speex header files missing])]) + AC_CHECK_LIB(speexdsp, main, [ + AC_CHECK_HEADERS([speex/speex_preprocess.h speex/speex_echo.h], [], + [AC_MSG_ERROR([speexdsp header files missing])]) + AC_DEFINE(HAVE_SPEEX, 1, [Define to 1 if you have the library.]) + LIBS="-lspeex -lspeexdsp $LIBS" + echo "LIBS += -lspeex" >> $QT_INCL_PRO + echo "LIBS += -lspeexdsp" >> $QT_INCL_PRO + have_speex="yes" + ], [have_speex="no"]) + ], [have_speex="no"]) +else + have_speex="no" +fi + +# iLBC +if test "x$ac_cv_ilbc" = "xyes" +then + AC_CHECK_LIB(ilbc, iLBC_decode, [ + AC_CHECK_HEADER(ilbc/iLBC_define.h, [], + [AC_MSG_ERROR([ilbc header files missing])]) + AC_DEFINE(HAVE_ILBC, 1, [Define to 1 if you have the library.]) + LIBS="-lilbc $LIBS" + echo "LIBS += -lilbc" >> $QT_INCL_PRO + have_ilbc="yes" + ], [have_ilbc="no"]) + + if test "x$ac_cv_ilbc_cpp" = "xyes" + then + AC_DEFINE(HAVE_ILBC_CPP, 1, [Define to 1 if you have a C++ ilbc library.]) + fi +else + have_ilbc="no" +fi + +# Check if zrtp is available +if test "x$ac_cv_zrtp" = "xyes" +then + PKG_CHECK_MODULES(ZRTP, libzrtpcpp >= 1.3.0) + AC_CHECK_LIB(zrtpcpp, main, [ + AC_CHECK_HEADER(libzrtpcpp/ZrtpQueue.h, [], + [AC_MSG_ERROR([zrtp header files missing])]) + AC_DEFINE(HAVE_ZRTP, 1, [Define to 1 if you have the library.]) + LIBS="-lzrtpcpp $LIBS" + echo "LIBS += -lzrtpcpp" >> $QT_INCL_PRO + have_zrtp="yes" + ], [have_zrtp="no"]) +else + have_zrtp="no" +fi + +# check if GNU readline or readline compatible libraries are avaliable +VL_LIB_READLINE() +if test "x$vl_cv_lib_readline" == "xno" +then + AC_MSG_ERROR([readline-devel package is not installed]) +fi + +# Check if boost regex is available +AC_CHECK_HEADER(boost/regex.h, [], + [AC_MSG_ERROR([boost/regex.h missing (boost-devel package)])]) +AC_CHECK_LIB(boost_regex, main, [ + LIBS="-lboost_regex $LIBS" + echo "LIBS += -lboost_regex" >> $QT_INCL_PRO], + [ + AC_CHECK_LIB(boost_regex-gcc, main, [ + LIBS="-lboost_regex-gcc $LIBS" + echo "LIBS += -lboost_regex-gcc" >> $QT_INCL_PRO], + [AC_MSG_ERROR([libboost_regex library is missing (boost package).])])]) + +ms_CHECK_LRELEASE() + +dnl $QTDIR/bin/qmake -o src/gui/Makefile src/gui/twinkle.pro +dnl Command above is incorrect because srcdir may differ from builddir +dnl +dnl $QTDIR/bin/qmake -o src/gui/Makefile $srcdir/src/gui/twinkle.pro +dnl Command above although is correct in both cases, i.e. when srcdir +dnl is equal or differ from builddir will not generate proper Makefile. +dnl Reason is unknown. +dnl +dnl The sed command adds the KDE3 libraries in front of the LIB path. +dnl This is needed to correctly build on a KDE4 system. Otherwise +dnl gcc will link against KDE4 libraries. +AC_CONFIG_COMMANDS([src/gui/Makefile], + [ + $QTDIR/bin/qmake -o src/gui/Makefile $srcdir/src/gui/twinkle.pro + echo "distdir:" >> src/gui/Makefile + echo "check:" >> src/gui/Makefile + + if test -n "$kde_libraries" + then + sed -i -e "s|\(LIBS *= \)|\1-L${kde_libraries} |" src/gui/Makefile + fi + ], + [kde_libraries=${kde_libraries}] +) + +dnl Next is useless. See comments in src/parser/Makefile.am . +dnl # Strip the -O2 flag from CXXFLAGS for building the SIP parser. +dnl # g++ cannot compile the generated C code from bison with -O2 or -Os +dnl # Note: -Os is used on VIA C3 processor +dnl PARSER_CXXFLAGS=`echo $CXXFLAGS | sed -e "s/\-O2//;s/\-Os//"` +dnl AC_SUBST(PARSER_CXXFLAGS) + +AC_OUTPUT(Makefile src/Makefile src/audio/Makefile src/audio/gsm/Makefile \ + src/audits/Makefile src/sdp/Makefile src/parser/Makefile \ + src/sockets/Makefile src/stun/Makefile src/threads/Makefile \ + src/gui/lang/Makefile src/mwi/Makefile src/im/Makefile \ + src/patterns/Makefile src/utils/Makefile src/presence/Makefile \ + twinkle.spec) + +AC_MSG_RESULT([]) +AC_MSG_RESULT([Configured optional components:]) +AC_MSG_RESULT([KDE support: $have_kde]) +AC_MSG_RESULT([ALSA: $have_libasound]) +AC_MSG_RESULT([Speex: $have_speex]) +AC_MSG_RESULT([iLBC: $have_ilbc]) +AC_MSG_RESULT([ZRTP: $have_zrtp]) diff --git a/data/providers.csv b/data/providers.csv new file mode 100644 index 0000000..260f82d --- /dev/null +++ b/data/providers.csv @@ -0,0 +1,6 @@ +# provider;domain;sip proxy;stun server +FreeWorld Dialup;fwd.pulver.com;;stun.fwdnet.net +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 diff --git a/data/ringback.wav b/data/ringback.wav new file mode 100644 index 0000000..a0b8f18 Binary files /dev/null and b/data/ringback.wav differ diff --git a/data/ringtone.wav b/data/ringtone.wav new file mode 100644 index 0000000..6f5eca9 Binary files /dev/null and b/data/ringtone.wav differ diff --git a/m4/vl_lib_readline.m4 b/m4/vl_lib_readline.m4 new file mode 100644 index 0000000..34d7bcc --- /dev/null +++ b/m4/vl_lib_readline.m4 @@ -0,0 +1,107 @@ +# =========================================================================== +# http://autoconf-archive.cryp.to/vl_lib_readline.html +# =========================================================================== +# +# SYNOPSIS +# +# VL_LIB_READLINE +# +# DESCRIPTION +# +# Searches for a readline compatible library. If found, defines +# `HAVE_LIBREADLINE'. If the found library has the `add_history' function, +# sets also `HAVE_READLINE_HISTORY'. Also checks for the locations of the +# necessary include files and sets `HAVE_READLINE_H' or +# `HAVE_READLINE_READLINE_H' and `HAVE_READLINE_HISTORY_H' or +# 'HAVE_HISTORY_H' if the corresponding include files exists. +# +# The libraries that may be readline compatible are `libedit', +# `libeditline' and `libreadline'. Sometimes we need to link a termcap +# library for readline to work, this macro tests these cases too by trying +# to link with `libtermcap', `libcurses' or `libncurses' before giving up. +# +# Here is an example of how to use the information provided by this macro +# to perform the necessary includes or declarations in a C file: +# +# #ifdef HAVE_LIBREADLINE +# # if defined(HAVE_READLINE_READLINE_H) +# # include +# # elif defined(HAVE_READLINE_H) +# # include +# # else /* !defined(HAVE_READLINE_H) */ +# extern char *readline (); +# # endif /* !defined(HAVE_READLINE_H) */ +# char *cmdline = NULL; +# #else /* !defined(HAVE_READLINE_READLINE_H) */ +# /* no readline */ +# #endif /* HAVE_LIBREADLINE */ +# +# #ifdef HAVE_READLINE_HISTORY +# # if defined(HAVE_READLINE_HISTORY_H) +# # include +# # elif defined(HAVE_HISTORY_H) +# # include +# # else /* !defined(HAVE_HISTORY_H) */ +# extern void add_history (); +# extern int write_history (); +# extern int read_history (); +# # endif /* defined(HAVE_READLINE_HISTORY_H) */ +# /* no history */ +# #endif /* HAVE_READLINE_HISTORY */ +# +# LAST MODIFICATION +# +# 2008-04-12 +# +# COPYLEFT +# +# Copyright (c) 2008 Ville Laurikari +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. + +AC_DEFUN([VL_LIB_READLINE], [ + AC_CACHE_CHECK([for a readline compatible library], + vl_cv_lib_readline, [ + ORIG_LIBS="$LIBS" + for readline_lib in readline edit editline; do + for termcap_lib in "" termcap curses ncurses; do + if test -z "$termcap_lib"; then + TRY_LIB="-l$readline_lib" + else + TRY_LIB="-l$readline_lib -l$termcap_lib" + fi + LIBS="$ORIG_LIBS $TRY_LIB" + AC_TRY_LINK_FUNC(readline, vl_cv_lib_readline="$TRY_LIB") + if test -n "$vl_cv_lib_readline"; then + break + fi + done + if test -n "$vl_cv_lib_readline"; then + break + fi + done + if test -z "$vl_cv_lib_readline"; then + vl_cv_lib_readline="no" + LIBS="$ORIG_LIBS" + fi + ]) + + if test "$vl_cv_lib_readline" != "no"; then + AC_DEFINE(HAVE_LIBREADLINE, 1, + [Define if you have a readline compatible library]) + AC_CHECK_HEADERS(readline.h readline/readline.h) + AC_CACHE_CHECK([whether readline supports history], + vl_cv_lib_readline_history, [ + vl_cv_lib_readline_history="no" + AC_TRY_LINK_FUNC(add_history, vl_cv_lib_readline_history="yes") + ]) + if test "$vl_cv_lib_readline_history" = "yes"; then + AC_DEFINE(HAVE_READLINE_HISTORY, 1, + [Define if your readline library has \`add_history']) + AC_CHECK_HEADERS(history.h readline/history.h) + fi + fi +])dnl + diff --git a/sip.protocol b/sip.protocol new file mode 100644 index 0000000..8002a76 --- /dev/null +++ b/sip.protocol @@ -0,0 +1,12 @@ +[Protocol] +exec=twinkle --call %u +protocol=sip +input=none +output=none +helper=true +listing= +reading=false +writing=false +makedir=false +deleting=false +Icon=multimedia diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..52a21db --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,104 @@ +AM_LDFLAGS = -L $(libdir) + +AM_CPPFLAGS = -Wall $(CCRTP_CFLAGS) $(XML2_CFLAGS) -DDATADIR=\"$(pkgdatadir)\" + +noinst_PROGRAMS = twinkle + +noinst_LIBRARIES = libtwinkle.a + +twinkle_SOURCES = main.cpp + +twinkle_LDADD = libtwinkle.a\ + $(top_builddir)/src/parser/libsipparser.a\ + $(top_builddir)/src/sdp/libsdpparser.a\ + $(top_builddir)/src/sockets/libsocket.a\ + $(top_builddir)/src/threads/libthread.a\ + $(top_builddir)/src/audio/libaudio.a\ + $(top_builddir)/src/audits/libaudits.a\ + $(top_builddir)/src/stun/libstun.a\ + $(top_builddir)/src/mwi/libmwi.a\ + $(top_builddir)/src/im/libim.a\ + $(top_builddir)/src/presence/libpresence.a\ + $(top_builddir)/src/patterns/libpatterns.a\ + $(top_builddir)/src/utils/libutils.a\ + $(GSM_LIBS)\ + $(CCRTP_LIBS)\ + $(XML2_LIBS)\ + -lncurses \ + -lreadline \ + -lsndfile\ + -lmagic + +libtwinkle_a_SOURCES =\ + 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\ + abstract_dialog.h\ + address_book.h\ + auth.h\ + call_history.h\ + call_script.h\ + client_request.h\ + cmd_socket.h\ + dialog.h\ + diamondcard.h\ + epa.h\ + events.h\ + exceptions.h\ + id_object.h\ + line.h\ + listener.h\ + log.h\ + phone.h\ + phone_user.h\ + prohibit_thread.h\ + protocol.h\ + redirect.h\ + sender.h\ + sequence_number.h\ + service.h\ + session.h\ + sub_refer.h\ + subscription.h\ + subscription_dialog.h\ + sys_settings.h\ + timekeeper.h\ + transaction.h\ + transaction_layer.h\ + transaction_mgr.h\ + translator.h\ + user.h\ + userintf.h\ + util.h + +SUBDIRS = audio audits im mwi parser patterns presence sdp sockets stun threads utils . gui gui/lang diff --git a/src/abstract_dialog.cpp b/src/abstract_dialog.cpp new file mode 100644 index 0000000..567c6a2 --- /dev/null +++ b/src/abstract_dialog.cpp @@ -0,0 +1,325 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include "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; +} diff --git a/src/abstract_dialog.h b/src/abstract_dialog.h new file mode 100644 index 0000000..aec18dc --- /dev/null +++ b/src/abstract_dialog.h @@ -0,0 +1,342 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 succesful 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 diff --git a/src/address_book.cpp b/src/address_book.cpp new file mode 100644 index 0000000..d27ad20 --- /dev/null +++ b/src/address_book.cpp @@ -0,0 +1,193 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/address_book.h b/src/address_book.h new file mode 100644 index 0000000..5a54c14 --- /dev/null +++ b/src/address_book.h @@ -0,0 +1,125 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 succesfully 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 diff --git a/src/audio/Makefile.am b/src/audio/Makefile.am new file mode 100644 index 0000000..ab897eb --- /dev/null +++ b/src/audio/Makefile.am @@ -0,0 +1,49 @@ +AM_CPPFLAGS = \ + -Wall \ + -I$(top_srcdir)/src \ + $(CCRTP_CFLAGS) \ + $(XML2_CFLAGS) + +noinst_LIBRARIES = libaudio.a + +libaudio_a_SOURCES =\ + 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\ + audio_device.h\ + audio_decoder.h\ + audio_encoder.h\ + audio_codecs.h\ + audio_rx.h\ + audio_session.h\ + audio_tx.h\ + dtmf_player.h\ + freq_gen.h\ + g711.h\ + g72x.h\ + media_buffer.h\ + rtp_telephone_event.h\ + tone_gen.h\ + twinkle_rtp_session.h\ + twinkle_zrtp_ui.h + +SUBDIRS = gsm + +EXTRA_DIST = README_G711 diff --git a/src/audio/README_G711 b/src/audio/README_G711 new file mode 100644 index 0000000..23b0e7d --- /dev/null +++ b/src/audio/README_G711 @@ -0,0 +1,94 @@ +The 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 + diff --git a/src/audio/audio_codecs.cpp b/src/audio/audio_codecs.cpp new file mode 100644 index 0000000..75a9262 --- /dev/null +++ b/src/audio/audio_codecs.cpp @@ -0,0 +1,99 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include "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_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); +} diff --git a/src/audio/audio_codecs.h b/src/audio/audio_codecs.h new file mode 100644 index 0000000..54967b1 --- /dev/null +++ b/src/audio/audio_codecs.h @@ -0,0 +1,107 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 +}; + +// 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 diff --git a/src/audio/audio_decoder.cpp b/src/audio/audio_decoder.cpp new file mode 100644 index 0000000..e4c1d74 --- /dev/null +++ b/src/audio/audio_decoder.cpp @@ -0,0 +1,523 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include "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; +} diff --git a/src/audio/audio_decoder.h b/src/audio/audio_decoder.h new file mode 100644 index 0000000..5fa9c55 --- /dev/null +++ b/src/audio/audio_decoder.h @@ -0,0 +1,201 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// Audio decoders + +#ifndef _AUDIO_DECODER_H +#define _AUDIO_DECODER_H + +#include +#include "twinkle_config.h" +#include "audio_codecs.h" +#include "user.h" + +#ifdef HAVE_GSM +#include +#else +#include "gsm/inc/gsm.h" +#endif + +#ifdef HAVE_SPEEX +#include +#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; +}; + +#endif diff --git a/src/audio/audio_device.cpp b/src/audio/audio_device.cpp new file mode 100644 index 0000000..3000a99 --- /dev/null +++ b/src/audio/audio_device.cpp @@ -0,0 +1,909 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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 diff --git a/src/audio/audio_device.h b/src/audio/audio_device.h new file mode 100644 index 0000000..27503f8 --- /dev/null +++ b/src/audio/audio_device.h @@ -0,0 +1,126 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/audio/audio_encoder.cpp b/src/audio/audio_encoder.cpp new file mode 100644 index 0000000..94e82b4 --- /dev/null +++ b/src/audio/audio_encoder.cpp @@ -0,0 +1,430 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include "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; +} diff --git a/src/audio/audio_encoder.h b/src/audio/audio_encoder.h new file mode 100644 index 0000000..d280567 --- /dev/null +++ b/src/audio/audio_encoder.h @@ -0,0 +1,183 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// Audio encoders + +#ifndef _AUDIO_ENCODER_H +#define _AUDIO_ENCODER_H + +#include +#include "twinkle_config.h" +#include "audio_codecs.h" +#include "user.h" + +#ifdef HAVE_GSM +#include +#else +#include "gsm/inc/gsm.h" +#endif + +#ifdef HAVE_SPEEX +#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); +}; + +#endif diff --git a/src/audio/audio_rx.cpp b/src/audio/audio_rx.cpp new file mode 100644 index 0000000..bb6db2b --- /dev/null +++ b/src/audio/audio_rx.cpp @@ -0,0 +1,915 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "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; + 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 (!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; +} diff --git a/src/audio/audio_rx.h b/src/audio/audio_rx.h new file mode 100644 index 0000000..5db8762 --- /dev/null +++ b/src/audio/audio_rx.h @@ -0,0 +1,217 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 { + uint8 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 diff --git a/src/audio/audio_session.cpp b/src/audio/audio_session.cpp new file mode 100644 index 0000000..539e2b1 --- /dev/null +++ b/src/audio/audio_session.cpp @@ -0,0 +1,677 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#include "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; +} diff --git a/src/audio/audio_session.h b/src/audio/audio_session.h new file mode 100644 index 0000000..422dbcd --- /dev/null +++ b/src/audio/audio_session.h @@ -0,0 +1,182 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/audio/audio_tx.cpp b/src/audio/audio_tx.cpp new file mode 100644 index 0000000..4901ede --- /dev/null +++ b/src/audio/audio_tx.cpp @@ -0,0 +1,1009 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include +#include "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]); + + 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; +} diff --git a/src/audio/audio_tx.h b/src/audio/audio_tx.h new file mode 100644 index 0000000..a51ef9a --- /dev/null +++ b/src/audio/audio_tx.h @@ -0,0 +1,205 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 "gsm/inc/gsm.h" +#include "audio_device.h" +#include "twinkle_rtp_session.h" +#include "twinkle_config.h" + +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 diff --git a/src/audio/dtmf_player.cpp b/src/audio/dtmf_player.cpp new file mode 100644 index 0000000..d3d5909 --- /dev/null +++ b/src/audio/dtmf_player.cpp @@ -0,0 +1,183 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include "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, uint8 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, + uint8 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, + uint8 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); +} diff --git a/src/audio/dtmf_player.h b/src/audio/dtmf_player.h new file mode 100644 index 0000000..5fa88db --- /dev/null +++ b/src/audio/dtmf_player.h @@ -0,0 +1,98 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// Classes to generate RTP payloads for DTMF tones. + +#ifndef _DTMF_PLAYER_H +#define _DTMF_PLAYER_H + +#include +#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 + uint8 _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, uint8 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, uint8 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, uint8 dtmf_tone, uint32 dtmf_timestamp, + uint16 nsamples); + + virtual uint16 get_payload(uint8 *payload, uint16 payload_size, + uint32 timestamp, uint32 &rtp_timestamp); +}; + +#endif diff --git a/src/audio/freq_gen.cpp b/src/audio/freq_gen.cpp new file mode 100644 index 0000000..5aad228 --- /dev/null +++ b/src/audio/freq_gen.cpp @@ -0,0 +1,134 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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(uint8 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); + } + + // 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)); + } +} diff --git a/src/audio/freq_gen.h b/src/audio/freq_gen.h new file mode 100644 index 0000000..5659739 --- /dev/null +++ b/src/audio/freq_gen.h @@ -0,0 +1,48 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 + +using namespace std; + +class t_freq_gen { +private: + vector _frequencies; + int16 _amplitude; + +public: + t_freq_gen(vector frequencies, int8 db_level); + t_freq_gen(uint8 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 diff --git a/src/audio/g711.cpp b/src/audio/g711.cpp new file mode 100644 index 0000000..f02ffe3 --- /dev/null +++ b/src/audio/g711.cpp @@ -0,0 +1,311 @@ +/* + * 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))); +} diff --git a/src/audio/g711.h b/src/audio/g711.h new file mode 100644 index 0000000..ed2f007 --- /dev/null +++ b/src/audio/g711.h @@ -0,0 +1,18 @@ +#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 diff --git a/src/audio/g721.cpp b/src/audio/g721.cpp new file mode 100644 index 0000000..a08dc79 --- /dev/null +++ b/src/audio/g721.cpp @@ -0,0 +1,174 @@ +/* + * 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); + } +} diff --git a/src/audio/g723_16.cpp b/src/audio/g723_16.cpp new file mode 100644 index 0000000..eae6aa1 --- /dev/null +++ b/src/audio/g723_16.cpp @@ -0,0 +1,181 @@ +/* + * 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); + } +} diff --git a/src/audio/g723_24.cpp b/src/audio/g723_24.cpp new file mode 100644 index 0000000..d70038a --- /dev/null +++ b/src/audio/g723_24.cpp @@ -0,0 +1,159 @@ +/* + * 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); + } +} diff --git a/src/audio/g723_40.cpp b/src/audio/g723_40.cpp new file mode 100644 index 0000000..d6ebd8a --- /dev/null +++ b/src/audio/g723_40.cpp @@ -0,0 +1,179 @@ +/* + * 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); + } +} diff --git a/src/audio/g72x.cpp b/src/audio/g72x.cpp new file mode 100644 index 0000000..40d1430 --- /dev/null +++ b/src/audio/g72x.cpp @@ -0,0 +1,565 @@ +/* + * 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); + } +} diff --git a/src/audio/g72x.h b/src/audio/g72x.h new file mode 100644 index 0000000..85201c3 --- /dev/null +++ b/src/audio/g72x.h @@ -0,0 +1,151 @@ +/* + * 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 */ diff --git a/src/audio/gsm/COPYRIGHT b/src/audio/gsm/COPYRIGHT new file mode 100644 index 0000000..eba0e52 --- /dev/null +++ b/src/audio/gsm/COPYRIGHT @@ -0,0 +1,16 @@ +Copyright 1992, 1993, 1994 by Jutta Degener and Carsten Bormann, +Technische Universitaet Berlin + +Any use of this software is permitted provided that this notice is not +removed and that neither the authors nor the Technische Universitaet Berlin +are deemed to have made any representations as to the suitability of this +software for any purpose nor are held responsible for any defects of +this software. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + +As a matter of courtesy, the authors request to be informed about uses +this software has found, about bugs in this software, and about any +improvements that may be of general interest. + +Berlin, 28.11.1994 +Jutta Degener +Carsten Bormann diff --git a/src/audio/gsm/ChangeLog b/src/audio/gsm/ChangeLog new file mode 100644 index 0000000..4cf467d --- /dev/null +++ b/src/audio/gsm/ChangeLog @@ -0,0 +1,80 @@ + +Fri Jul 5 19:26:37 1996 Jutta Degener (jutta@cs.tu-berlin.de) + + * Release 1.0 Patchlevel 10 + src/toast_alaw.c: exchanged A-law tables for something + slightly more A-law. + +Tue Jul 2 12:18:20 1996 Jutta Degener (jutta@cs.tu-berlin.de) + + * Release 1.0 Patchlevel 9 + src/long_term.c: in FLOAT_MUL mode, an array was accessed past its end + src/gsm_option.c: three options related to WAV #49 packing + src/gsm_encode.c: support WAV #49-style encoding. + src/gsm_decode.c: support WAV #49-style decoding. + tls/sour.c: generate the WAV bit shifting code, encode + tls/ginger.c: generate the WAV bit shifting code, decode + The WAV code goes back to an inofficial patch #8 that + Jeff Chilton sent us (hence the jump from 7 to 9). + src/toast.c: add _fsetmode() calls to set stdin/stdout to + binary (from an OS/2 port by Arnd Gronenberg.) + +Tue Mar 7 01:55:10 1995 Jutta Degener (jutta@cs.tu-berlin.de) + + * Release 1.0 Patchlevel 7 + src/long_term.c: Yet another 16-bit overflow + src/toast.c: -C option to toast, cuts LPC time + src/gsm_option.c: corresponding LPC_CUT option to GSM library + +Fri Dec 30 23:33:50 1994 Jutta Degener (jutta@cs.tu-berlin.de) + + * Release 1.0 Patchlevel 6 + src/lpc.c: fixed 16-bit addition overflow in Autocorrelation code + src/add.c: gsm_L_asl should fall back on gsm_L_asr, not gsm_asr + +Mon Nov 28 20:49:57 1994 Jutta Degener (jutta@cs.tu-berlin.de) + + * Release 1.0 Patchlevel 5 + src/toast_audio.c: initialization should return -1 on error + src/gsm_destroy.c: #include configuration header file + src/add.c: gsm_sub should cast its parameters to longword + man/*: bug reports to {jutta,cabo}@cs.tu-berlin.de, not to toast@tub + inc/private.h: longword long by default, not int + inc/toast.h: read/write fopen modes "rb" and "wb", not just "r" + src/toast.c: better (or different, anyway) error handling in process() + +Tue May 10 19:41:34 1994 Jutta Degener (jutta at kugelbus) + + * Release 1.0 Patchlevel 4 + inc/private.h: GSM_ADD should cast to ulongword, not to unsigned. + src/long_term.c: missing cast to longword. + add-test/add_test.c: Test macros too, not only functions, + thanks to Simao Ferraz de Campos Neto, simao@dragon.cpqd.ansp.br + General cleanup: remove unused variables, add function prototypes. + +Tue Jan 25 22:53:40 1994 Jutta Degener (jutta at kugelbus) + + * Release 1.0 Patchlevel 3 + changed rpe.c's STEP macro to work with 16-bit integers, + thanks to Dr Alex Lee (alexlee@solomon.technet.sg); + removed non-fatal bugs from add-test.dta, private.h + and toast_audio.c, thanks to P. Emanuelsson. + +Fri Jan 29 19:02:12 1993 Jutta Degener (jutta at kraftbus) + + * Release 1.0 Patchlevel 2 + fixed L_add(0,-1) in src/add.c and inc/private.h, + thanks to Raphael Trommer at AT&T Bell Laboratories; + various other ANSI C compatibility details + +Fri Oct 30 17:58:54 1992 Jutta Degener (jutta at kraftbus) + + * Release 1.0 Patchlevel 1 + Switched uid/gid in toast's [f]chown calls. + +Wed Oct 28 14:12:35 1992 Carsten Bormann (cabo at kubus) + + * Release 1.0: released + Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + Universitaet Berlin. See the accompanying file "COPYRIGHT" for + details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. diff --git a/src/audio/gsm/INSTALL b/src/audio/gsm/INSTALL new file mode 100644 index 0000000..5850304 --- /dev/null +++ b/src/audio/gsm/INSTALL @@ -0,0 +1,99 @@ +How to get started: + + Edit the Makefile. + + You should configure a few machine-dependencies and what + compiler you want to use. + + The code works both with ANSI and K&R-C. Use + -DNeedFunctionPrototypes to compile with, or + -UNeedFunctionPrototypes to compile without, function + prototypes in the header files. + + Make addtst + + The "add" program that will be compiled and run checks whether + the basic math functions of the gsm library work with your + compiler. If it prints anything to stderr, complain (to us). + + Edit inc/config.h. + + Make + + Local versions of the gsm library and the "compress"-like filters + toast, untoast and tcat will be generated. + + If the compilation aborts because of a missing function, + declaration, or header file, see if there's something in + inc/config.h to work around it. If not, complain. + + Try it + + Grab an audio file from somewhere (raw u-law or Sun .au is fine, + linear 16-bit in host byte order will do), copy it, toast it, + untoast it, and listen to the result. + + The GSM-encoded and -decoded audio should have the quality + of a good phone line. If the resulting audio is noisier than + your original, or if you hear compression artifacts, complain; + that's a bug in our software, not a bug in the GSM encoding + standard itself. + +Installation + + You can install the gsm library interface, or the toast binaries, + or both. + + Edit the Makefile + + Fill in the directories where you want to install the + library, header files, manual pages, and binaries. + + Turn off the installation of one half of the distribution + (i.e., gsm library or toast binaries) by not setting the + corresponding directory root Makefile macro. + + make install + + will install the programs "toast" with two links named + "tcat" and "untoast", and the gsm library "libgsm.a" with + a "gsm.h" header file, and their respective manual pages. + + +Optimizing + + This code was developed on a machine without an integer + multiplication instruction, where we obtained the fastest result by + replacing some of the integer multiplications with floating point + multiplications. + + If your machine does multiply integers fast enough, + leave USE_FLOAT_MUL undefined. The results should be the + same in both cases. + + On machines with fast floating point arithmetic, defining + both USE_FLOAT_MUL and FAST makes a run-time library + option available that will (in a few crucial places) use + ``native'' floating point operations rather than the bit-by-bit + defined ones of the GSM standard. If you use this fast + option, the outcome will not be bitwise identical to the + results prescribed by the standard, but it is compatible with + the standard encoding, and a user is unlikely to notice a + difference. + + +Bug Reports + + Please direct bug reports, questions, and comments to + jutta@cs.tu-berlin.de and cabo@informatik.uni-bremen.de. + + +Good luck, + + Jutta Degener, + Carsten Bormann + +-- +Copyright 1992, 1993, 1994, by Jutta Degener and Carsten Bormann, +Technische Universitaet Berlin. See the accompanying file "COPYRIGHT" +for details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. diff --git a/src/audio/gsm/MACHINES b/src/audio/gsm/MACHINES new file mode 100644 index 0000000..4adafd2 --- /dev/null +++ b/src/audio/gsm/MACHINES @@ -0,0 +1,11 @@ +The gsm library has been tested successfully on the following platforms: + +- Various Sun4's running SunOS 4.1.2 +- SPARC1 (SunOS 4.1.1) +- Integrated Solutions 68k Optimum running 4.3BSD UNIX with a Green Hills cc +- NeXTstation running NeXT-OS/Mach 3.0 +- No-name AT/386 with Xenix 2.3.2 (using -DSTUPID_COMPILER) +- RS/6000-350 running AIX 3.2.0 +- RS/6000-320 running AIX 3.1.5 +- Alliant FX80 (Concentrix 5.7) +- SGI Indigo XS4000 (IRIX 4.0.5F) diff --git a/src/audio/gsm/Makefile.am b/src/audio/gsm/Makefile.am new file mode 100644 index 0000000..3d437a1 --- /dev/null +++ b/src/audio/gsm/Makefile.am @@ -0,0 +1,32 @@ +AM_CPPFLAGS = \ + -I$(srcdir)/inc \ + -DSASR -DNeedFunctionPrototypes=1 + +noinst_LIBRARIES = libgsm.a + +libgsm_a_SOURCES =\ + src/add.cpp\ + src/code.cpp\ + src/debug.cpp\ + src/decode.cpp\ + src/long_term.cpp\ + src/lpc.cpp\ + src/preprocess.cpp\ + src/rpe.cpp\ + src/gsm_destroy.cpp\ + src/gsm_decode.cpp\ + src/gsm_encode.cpp\ + src/gsm_explode.cpp\ + src/gsm_implode.cpp\ + src/gsm_create.cpp\ + src/gsm_print.cpp\ + src/gsm_option.cpp\ + src/short_term.cpp\ + src/table.cpp\ + inc/gsm.h\ + inc/proto.h\ + inc/config.h\ + inc/private.h\ + inc/unproto.h + +EXTRA_DIST = COPYRIGHT MACHINES diff --git a/src/audio/gsm/README b/src/audio/gsm/README new file mode 100644 index 0000000..cb6af85 --- /dev/null +++ b/src/audio/gsm/README @@ -0,0 +1,37 @@ + +GSM 06.10 13 kbit/s RPE/LTP speech compression available +-------------------------------------------------------- + +The Communications and Operating Systems Research Group (KBS) at the +Technische Universitaet Berlin is currently working on a set of +UNIX-based tools for computer-mediated telecooperation that will be +made freely available. + +As part of this effort we are publishing an implementation of the +European GSM 06.10 provisional standard for full-rate speech +transcoding, prI-ETS 300 036, which uses RPE/LTP (residual pulse +excitation/long term prediction) coding at 13 kbit/s. + +GSM 06.10 compresses frames of 160 13-bit samples (8 kHz sampling +rate, i.e. a frame rate of 50 Hz) into 260 bits; for compatibility +with typical UNIX applications, our implementation turns frames of 160 +16-bit linear samples into 33-byte frames (1650 Bytes/s). +The quality of the algorithm is good enough for reliable speaker +recognition; even music often survives transcoding in recognizable +form (given the bandwidth limitations of 8 kHz sampling rate). + +The interfaces offered are a front end modelled after compress(1), and +a library API. Compression and decompression run faster than realtime +on most SPARCstations. The implementation has been verified against the +ETSI standard test patterns. + +Jutta Degener (jutta@cs.tu-berlin.de) +Carsten Bormann (cabo@cs.tu-berlin.de) + +Communications and Operating Systems Research Group, TU Berlin +Fax: +49.30.31425156, Phone: +49.30.31424315 + +-- +Copyright 1992 by Jutta Degener and Carsten Bormann, Technische +Universitaet Berlin. See the accompanying file "COPYRIGHT" for +details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. diff --git a/src/audio/gsm/inc/config.h b/src/audio/gsm/inc/config.h new file mode 100644 index 0000000..6aec1ae --- /dev/null +++ b/src/audio/gsm/inc/config.h @@ -0,0 +1,37 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/*$Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/config.h,v 1.5 1996/07/02 11:26:20 jutta Exp $*/ + +#ifndef CONFIG_H +#define CONFIG_H + +/*efine SIGHANDLER_T int /* signal handlers are void */ +/*efine HAS_SYSV_SIGNAL 1 /* sigs not blocked/reset? */ + +#define HAS_STDLIB_H 1 /* /usr/include/stdlib.h */ +/*efine HAS_LIMITS_H 1 /* /usr/include/limits.h */ +#define HAS_FCNTL_H 1 /* /usr/include/fcntl.h */ +/*efine HAS_ERRNO_DECL 1 /* errno.h declares errno */ + +#define HAS_FSTAT 1 /* fstat syscall */ +#define HAS_FCHMOD 1 /* fchmod syscall */ +#define HAS_CHMOD 1 /* chmod syscall */ +#define HAS_FCHOWN 1 /* fchown syscall */ +#define HAS_CHOWN 1 /* chown syscall */ +/*efine HAS__FSETMODE 1 /* _fsetmode -- set file mode */ + +#define HAS_STRING_H 1 /* /usr/include/string.h */ +/*efine HAS_STRINGS_H 1 /* /usr/include/strings.h */ + +#define HAS_UNISTD_H 1 /* /usr/include/unistd.h */ +#define HAS_UTIME 1 /* POSIX utime(path, times) */ +/*efine HAS_UTIMES 1 /* use utimes() syscall instead */ +#define HAS_UTIME_H 1 /* UTIME header file */ +/*efine HAS_UTIMBUF 1 /* struct utimbuf */ +/*efine HAS_UTIMEUSEC 1 /* microseconds in utimbuf? */ + +#endif /* CONFIG_H */ diff --git a/src/audio/gsm/inc/gsm.h b/src/audio/gsm/inc/gsm.h new file mode 100644 index 0000000..4714ab6 --- /dev/null +++ b/src/audio/gsm/inc/gsm.h @@ -0,0 +1,71 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/*$Header: /home/kbs/jutta/src/gsm/gsm-1.0/inc/RCS/gsm.h,v 1.11 1996/07/05 18:02:56 jutta Exp $*/ + +#ifndef GSM_H +#define GSM_H + +#ifdef __cplusplus +# define NeedFunctionPrototypes 1 +#endif + +#if __STDC__ +# define NeedFunctionPrototypes 1 +#endif + +#ifdef _NO_PROTO +# undef NeedFunctionPrototypes +#endif + +#ifdef NeedFunctionPrototypes +# include /* for FILE * */ +#endif + +#undef GSM_P +#if NeedFunctionPrototypes +# define GSM_P( protos ) protos +#else +# define GSM_P( protos ) ( /* protos */ ) +#endif + +/* + * Interface + */ + +typedef struct gsm_state * gsm; +typedef short gsm_signal; /* signed 16 bit */ +typedef unsigned char gsm_byte; +typedef gsm_byte gsm_frame[33]; /* 33 * 8 bits */ + +#define GSM_MAGIC 0xD /* 13 kbit/s RPE-LTP */ + +#define GSM_PATCHLEVEL 10 +#define GSM_MINOR 0 +#define GSM_MAJOR 1 + +#define GSM_OPT_VERBOSE 1 +#define GSM_OPT_FAST 2 +#define GSM_OPT_LTP_CUT 3 +#define GSM_OPT_WAV49 4 +#define GSM_OPT_FRAME_INDEX 5 +#define GSM_OPT_FRAME_CHAIN 6 + +extern gsm gsm_create GSM_P((void)); +extern void gsm_destroy GSM_P((gsm)); + +extern int gsm_print GSM_P((FILE *, gsm, gsm_byte *)); +extern int gsm_option GSM_P((gsm, int, int *)); + +extern void gsm_encode GSM_P((gsm, gsm_signal *, gsm_byte *)); +extern int gsm_decode GSM_P((gsm, gsm_byte *, gsm_signal *)); + +extern int gsm_explode GSM_P((gsm, gsm_byte *, gsm_signal *)); +extern void gsm_implode GSM_P((gsm, gsm_signal *, gsm_byte *)); + +#undef GSM_P + +#endif /* GSM_H */ diff --git a/src/audio/gsm/inc/private.h b/src/audio/gsm/inc/private.h new file mode 100644 index 0000000..fea5694 --- /dev/null +++ b/src/audio/gsm/inc/private.h @@ -0,0 +1,268 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/*$Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/private.h,v 1.6 1996/07/02 10:15:26 jutta Exp $*/ + +#ifndef PRIVATE_H +#define PRIVATE_H + +typedef short word; /* 16 bit signed int */ +typedef long longword; /* 32 bit signed int */ + +typedef unsigned short uword; /* unsigned word */ +typedef unsigned long ulongword; /* unsigned longword */ + +struct gsm_state { + + word dp0[ 280 ]; + + word z1; /* preprocessing.c, Offset_com. */ + longword L_z2; /* Offset_com. */ + int mp; /* Preemphasis */ + + word u[8]; /* short_term_aly_filter.c */ + word LARpp[2][8]; /* */ + word j; /* */ + + word ltp_cut; /* long_term.c, LTP crosscorr. */ + word nrp; /* 40 */ /* long_term.c, synthesis */ + word v[9]; /* short_term.c, synthesis */ + word msr; /* decoder.c, Postprocessing */ + + char verbose; /* only used if !NDEBUG */ + char fast; /* only used if FAST */ + + char wav_fmt; /* only used if WAV49 defined */ + unsigned char frame_index; /* odd/even chaining */ + unsigned char frame_chain; /* half-byte to carry forward */ +}; + + +#define MIN_WORD (-32767 - 1) +#define MAX_WORD 32767 + +#define MIN_LONGWORD (-2147483647 - 1) +#define MAX_LONGWORD 2147483647 + +#ifdef SASR /* flag: >> is a signed arithmetic shift right */ +#undef SASR +#define SASR(x, by) ((x) >> (by)) +#else +#define SASR(x, by) ((x) >= 0 ? (x) >> (by) : (~(-((x) + 1) >> (by)))) +#endif /* SASR */ + +#include "proto.h" + +/* + * Prototypes from add.c + */ +extern word gsm_mult P((word a, word b)); +extern longword gsm_L_mult P((word a, word b)); +extern word gsm_mult_r P((word a, word b)); + +extern word gsm_div P((word num, word denum)); + +extern word gsm_add P(( word a, word b )); +extern longword gsm_L_add P(( longword a, longword b )); + +extern word gsm_sub P((word a, word b)); +extern longword gsm_L_sub P((longword a, longword b)); + +extern word gsm_abs P((word a)); + +extern word gsm_norm P(( longword a )); + +extern longword gsm_L_asl P((longword a, int n)); +extern word gsm_asl P((word a, int n)); + +extern longword gsm_L_asr P((longword a, int n)); +extern word gsm_asr P((word a, int n)); + +/* + * Inlined functions from add.h + */ + +/* + * #define GSM_MULT_R(a, b) (* word a, word b, !(a == b == MIN_WORD) *) \ + * (0x0FFFF & SASR(((longword)(a) * (longword)(b) + 16384), 15)) + */ +#define GSM_MULT_R(a, b) /* word a, word b, !(a == b == MIN_WORD) */ \ + (SASR( ((longword)(a) * (longword)(b) + 16384), 15 )) + +# define GSM_MULT(a,b) /* word a, word b, !(a == b == MIN_WORD) */ \ + (SASR( ((longword)(a) * (longword)(b)), 15 )) + +# define GSM_L_MULT(a, b) /* word a, word b */ \ + (((longword)(a) * (longword)(b)) << 1) + +# define GSM_L_ADD(a, b) \ + ( (a) < 0 ? ( (b) >= 0 ? (a) + (b) \ + : (utmp = (ulongword)-((a) + 1) + (ulongword)-((b) + 1)) \ + >= MAX_LONGWORD ? MIN_LONGWORD : -(longword)utmp-2 ) \ + : ((b) <= 0 ? (a) + (b) \ + : (utmp = (ulongword)(a) + (ulongword)(b)) >= MAX_LONGWORD \ + ? MAX_LONGWORD : utmp)) + +/* + * # define GSM_ADD(a, b) \ + * ((ltmp = (longword)(a) + (longword)(b)) >= MAX_WORD \ + * ? MAX_WORD : ltmp <= MIN_WORD ? MIN_WORD : ltmp) + */ +/* Nonportable, but faster: */ + +#define GSM_ADD(a, b) \ + ((ulongword)((ltmp = (longword)(a) + (longword)(b)) - MIN_WORD) > \ + MAX_WORD - MIN_WORD ? (ltmp > 0 ? MAX_WORD : MIN_WORD) : ltmp) + +# define GSM_SUB(a, b) \ + ((ltmp = (longword)(a) - (longword)(b)) >= MAX_WORD \ + ? MAX_WORD : ltmp <= MIN_WORD ? MIN_WORD : ltmp) + +# define GSM_ABS(a) ((a) < 0 ? ((a) == MIN_WORD ? MAX_WORD : -(a)) : (a)) + +/* Use these if necessary: + +# define GSM_MULT_R(a, b) gsm_mult_r(a, b) +# define GSM_MULT(a, b) gsm_mult(a, b) +# define GSM_L_MULT(a, b) gsm_L_mult(a, b) + +# define GSM_L_ADD(a, b) gsm_L_add(a, b) +# define GSM_ADD(a, b) gsm_add(a, b) +# define GSM_SUB(a, b) gsm_sub(a, b) + +# define GSM_ABS(a) gsm_abs(a) + +*/ + +/* + * More prototypes from implementations.. + */ +extern void Gsm_Coder P(( + struct gsm_state * S, + word * s, /* [0..159] samples IN */ + word * LARc, /* [0..7] LAR coefficients OUT */ + word * Nc, /* [0..3] LTP lag OUT */ + word * bc, /* [0..3] coded LTP gain OUT */ + word * Mc, /* [0..3] RPE grid selection OUT */ + word * xmaxc,/* [0..3] Coded maximum amplitude OUT */ + word * xMc /* [13*4] normalized RPE samples OUT */)); + +extern void Gsm_Long_Term_Predictor P(( /* 4x for 160 samples */ + struct gsm_state * S, + word * d, /* [0..39] residual signal IN */ + word * dp, /* [-120..-1] d' IN */ + word * e, /* [0..40] OUT */ + word * dpp, /* [0..40] OUT */ + word * Nc, /* correlation lag OUT */ + word * bc /* gain factor OUT */)); + +extern void Gsm_LPC_Analysis P(( + struct gsm_state * S, + word * s, /* 0..159 signals IN/OUT */ + word * LARc)); /* 0..7 LARc's OUT */ + +extern void Gsm_Preprocess P(( + struct gsm_state * S, + word * s, word * so)); + +extern void Gsm_Encoding P(( + struct gsm_state * S, + word * e, + word * ep, + word * xmaxc, + word * Mc, + word * xMc)); + +extern void Gsm_Short_Term_Analysis_Filter P(( + struct gsm_state * S, + word * LARc, /* coded log area ratio [0..7] IN */ + word * d /* st res. signal [0..159] IN/OUT */)); + +extern void Gsm_Decoder P(( + struct gsm_state * S, + word * LARcr, /* [0..7] IN */ + word * Ncr, /* [0..3] IN */ + word * bcr, /* [0..3] IN */ + word * Mcr, /* [0..3] IN */ + word * xmaxcr, /* [0..3] IN */ + word * xMcr, /* [0..13*4] IN */ + word * s)); /* [0..159] OUT */ + +extern void Gsm_Decoding P(( + struct gsm_state * S, + word xmaxcr, + word Mcr, + word * xMcr, /* [0..12] IN */ + word * erp)); /* [0..39] OUT */ + +extern void Gsm_Long_Term_Synthesis_Filtering P(( + struct gsm_state* S, + word Ncr, + word bcr, + word * erp, /* [0..39] IN */ + word * drp)); /* [-120..-1] IN, [0..40] OUT */ + +void Gsm_RPE_Decoding P(( + struct gsm_state *S, + word xmaxcr, + word Mcr, + word * xMcr, /* [0..12], 3 bits IN */ + word * erp)); /* [0..39] OUT */ + +void Gsm_RPE_Encoding P(( + struct gsm_state * S, + word * e, /* -5..-1][0..39][40..44 IN/OUT */ + word * xmaxc, /* OUT */ + word * Mc, /* OUT */ + word * xMc)); /* [0..12] OUT */ + +extern void Gsm_Short_Term_Synthesis_Filter P(( + struct gsm_state * S, + word * LARcr, /* log area ratios [0..7] IN */ + word * drp, /* received d [0...39] IN */ + word * s)); /* signal s [0..159] OUT */ + +extern void Gsm_Update_of_reconstructed_short_time_residual_signal P(( + word * dpp, /* [0...39] IN */ + word * ep, /* [0...39] IN */ + word * dp)); /* [-120...-1] IN/OUT */ + +/* + * Tables from table.c + */ +#ifndef GSM_TABLE_C + +extern word gsm_A[8], gsm_B[8], gsm_MIC[8], gsm_MAC[8]; +extern word gsm_INVA[8]; +extern word gsm_DLB[4], gsm_QLB[4]; +extern word gsm_H[11]; +extern word gsm_NRFAC[8]; +extern word gsm_FAC[8]; + +#endif /* GSM_TABLE_C */ + +/* + * Debugging + */ +#ifdef NDEBUG + +# define gsm_debug_words(a, b, c, d) /* nil */ +# define gsm_debug_longwords(a, b, c, d) /* nil */ +# define gsm_debug_word(a, b) /* nil */ +# define gsm_debug_longword(a, b) /* nil */ + +#else /* !NDEBUG => DEBUG */ + + extern void gsm_debug_words P((char * name, int, int, word *)); + extern void gsm_debug_longwords P((char * name, int, int, longword *)); + extern void gsm_debug_longword P((char * name, longword)); + extern void gsm_debug_word P((char * name, word)); + +#endif /* !NDEBUG */ + +#include "unproto.h" + +#endif /* PRIVATE_H */ diff --git a/src/audio/gsm/inc/proto.h b/src/audio/gsm/inc/proto.h new file mode 100644 index 0000000..2851c08 --- /dev/null +++ b/src/audio/gsm/inc/proto.h @@ -0,0 +1,65 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/*$Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/proto.h,v 1.1 1992/10/28 00:11:08 jutta Exp $*/ + +#ifndef PROTO_H +#define PROTO_H + +#if __cplusplus +# define NeedFunctionPrototypes 1 +#endif + +#if __STDC__ +# define NeedFunctionPrototypes 1 +#endif + +#ifdef _NO_PROTO +# undef NeedFunctionPrototypes +#endif + +#undef P /* gnu stdio.h actually defines this... */ +#undef P0 +#undef P1 +#undef P2 +#undef P3 +#undef P4 +#undef P5 +#undef P6 +#undef P7 +#undef P8 + +#if NeedFunctionPrototypes + +# define P( protos ) protos + +# define P0() (void) +# define P1(x, a) (a) +# define P2(x, a, b) (a, b) +# define P3(x, a, b, c) (a, b, c) +# define P4(x, a, b, c, d) (a, b, c, d) +# define P5(x, a, b, c, d, e) (a, b, c, d, e) +# define P6(x, a, b, c, d, e, f) (a, b, c, d, e, f) +# define P7(x, a, b, c, d, e, f, g) (a, b, c, d, e, f, g) +# define P8(x, a, b, c, d, e, f, g, h) (a, b, c, d, e, f, g, h) + +#else /* !NeedFunctionPrototypes */ + +# define P( protos ) ( /* protos */ ) + +# define P0() () +# define P1(x, a) x a; +# define P2(x, a, b) x a; b; +# define P3(x, a, b, c) x a; b; c; +# define P4(x, a, b, c, d) x a; b; c; d; +# define P5(x, a, b, c, d, e) x a; b; c; d; e; +# define P6(x, a, b, c, d, e, f) x a; b; c; d; e; f; +# define P7(x, a, b, c, d, e, f, g) x a; b; c; d; e; f; g; +# define P8(x, a, b, c, d, e, f, g, h) x a; b; c; d; e; f; g; h; + +#endif /* !NeedFunctionPrototypes */ + +#endif /* PROTO_H */ diff --git a/src/audio/gsm/inc/unproto.h b/src/audio/gsm/inc/unproto.h new file mode 100644 index 0000000..eaf866f --- /dev/null +++ b/src/audio/gsm/inc/unproto.h @@ -0,0 +1,23 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/*$Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/unproto.h,v 1.1 1992/10/28 00:11:08 jutta Exp $*/ + +#ifdef PROTO_H /* sic */ +#undef PROTO_H + +#undef P +#undef P0 +#undef P1 +#undef P2 +#undef P3 +#undef P4 +#undef P5 +#undef P6 +#undef P7 +#undef P8 + +#endif /* PROTO_H */ diff --git a/src/audio/gsm/src/add.cpp b/src/audio/gsm/src/add.cpp new file mode 100644 index 0000000..258a6fc --- /dev/null +++ b/src/audio/gsm/src/add.cpp @@ -0,0 +1,235 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/* $Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/add.c,v 1.6 1996/07/02 09:57:33 jutta Exp $ */ + +/* + * See private.h for the more commonly used macro versions. + */ + +#include +#include + +#include "private.h" +#include "gsm.h" +#include "proto.h" + +#define saturate(x) \ + ((x) < MIN_WORD ? MIN_WORD : (x) > MAX_WORD ? MAX_WORD: (x)) + +word gsm_add P2((a,b), word a, word b) +{ + longword sum = (longword)a + (longword)b; + return saturate(sum); +} + +word gsm_sub P2((a,b), word a, word b) +{ + longword diff = (longword)a - (longword)b; + return saturate(diff); +} + +word gsm_mult P2((a,b), word a, word b) +{ + if (a == MIN_WORD && b == MIN_WORD) return MAX_WORD; + else return SASR( (longword)a * (longword)b, 15 ); +} + +word gsm_mult_r P2((a,b), word a, word b) +{ + if (b == MIN_WORD && a == MIN_WORD) return MAX_WORD; + else { + longword prod = (longword)a * (longword)b + 16384; + prod >>= 15; + return prod & 0xFFFF; + } +} + +word gsm_abs P1((a), word a) +{ + return a < 0 ? (a == MIN_WORD ? MAX_WORD : -a) : a; +} + +longword gsm_L_mult P2((a,b),word a, word b) +{ + assert( a != MIN_WORD || b != MIN_WORD ); + return ((longword)a * (longword)b) << 1; +} + +longword gsm_L_add P2((a,b), longword a, longword b) +{ + if (a < 0) { + if (b >= 0) return a + b; + else { + ulongword A = (ulongword)-(a + 1) + (ulongword)-(b + 1); + return A >= MAX_LONGWORD ? MIN_LONGWORD :-(longword)A-2; + } + } + else if (b <= 0) return a + b; + else { + ulongword A = (ulongword)a + (ulongword)b; + return A > MAX_LONGWORD ? MAX_LONGWORD : A; + } +} + +longword gsm_L_sub P2((a,b), longword a, longword b) +{ + if (a >= 0) { + if (b >= 0) return a - b; + else { + /* a>=0, b<0 */ + + ulongword A = (ulongword)a + -(b + 1); + return A >= MAX_LONGWORD ? MAX_LONGWORD : (A + 1); + } + } + else if (b <= 0) return a - b; + else { + /* a<0, b>0 */ + + ulongword A = (ulongword)-(a + 1) + b; + return A >= MAX_LONGWORD ? MIN_LONGWORD : -(longword)A - 1; + } +} + +static unsigned char const bitoff[ 256 ] = { + 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +word gsm_norm P1((a), longword a ) +/* + * the number of left shifts needed to normalize the 32 bit + * variable L_var1 for positive values on the interval + * + * with minimum of + * minimum of 1073741824 (01000000000000000000000000000000) and + * maximum of 2147483647 (01111111111111111111111111111111) + * + * + * and for negative values on the interval with + * minimum of -2147483648 (-10000000000000000000000000000000) and + * maximum of -1073741824 ( -1000000000000000000000000000000). + * + * in order to normalize the result, the following + * operation must be done: L_norm_var1 = L_var1 << norm( L_var1 ); + * + * (That's 'ffs', only from the left, not the right..) + */ +{ + assert(a != 0); + + if (a < 0) { + if (a <= -1073741824) return 0; + a = ~a; + } + + return a & 0xffff0000 + ? ( a & 0xff000000 + ? -1 + bitoff[ 0xFF & (a >> 24) ] + : 7 + bitoff[ 0xFF & (a >> 16) ] ) + : ( a & 0xff00 + ? 15 + bitoff[ 0xFF & (a >> 8) ] + : 23 + bitoff[ 0xFF & a ] ); +} + +longword gsm_L_asl P2((a,n), longword a, int n) +{ + if (n >= 32) return 0; + if (n <= -32) return -(a < 0); + if (n < 0) return gsm_L_asr(a, -n); + return a << n; +} + +word gsm_asl P2((a,n), word a, int n) +{ + if (n >= 16) return 0; + if (n <= -16) return -(a < 0); + if (n < 0) return gsm_asr(a, -n); + return a << n; +} + +longword gsm_L_asr P2((a,n), longword a, int n) +{ + if (n >= 32) return -(a < 0); + if (n <= -32) return 0; + if (n < 0) return a << -n; + +# ifdef SASR + return a >> n; +# else + if (a >= 0) return a >> n; + else return -(longword)( -(ulongword)a >> n ); +# endif +} + +word gsm_asr P2((a,n), word a, int n) +{ + if (n >= 16) return -(a < 0); + if (n <= -16) return 0; + if (n < 0) return a << -n; + +# ifdef SASR + return a >> n; +# else + if (a >= 0) return a >> n; + else return -(word)( -(uword)a >> n ); +# endif +} + +/* + * (From p. 46, end of section 4.2.5) + * + * NOTE: The following lines gives [sic] one correct implementation + * of the div(num, denum) arithmetic operation. Compute div + * which is the integer division of num by denum: with denum + * >= num > 0 + */ + +word gsm_div P2((num,denum), word num, word denum) +{ + longword L_num = num; + longword L_denum = denum; + word div = 0; + int k = 15; + + /* The parameter num sometimes becomes zero. + * Although this is explicitly guarded against in 4.2.5, + * we assume that the result should then be zero as well. + */ + + /* assert(num != 0); */ + + assert(num >= 0 && denum >= num); + if (num == 0) + return 0; + + while (k--) { + div <<= 1; + L_num <<= 1; + + if (L_num >= L_denum) { + L_num -= L_denum; + div++; + } + } + + return div; +} diff --git a/src/audio/gsm/src/code.cpp b/src/audio/gsm/src/code.cpp new file mode 100644 index 0000000..5082829 --- /dev/null +++ b/src/audio/gsm/src/code.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/* $Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/code.c,v 1.3 1996/07/02 09:59:05 jutta Exp $ */ + +#include "config.h" +#include + + +#ifdef HAS_STDLIB_H +#include +#else +# include "proto.h" + extern char * memcpy P((char *, char *, int)); +#endif + +#include "private.h" +#include "gsm.h" +#include "proto.h" + +/* + * 4.2 FIXED POINT IMPLEMENTATION OF THE RPE-LTP CODER + */ + +void Gsm_Coder P8((S,s,LARc,Nc,bc,Mc,xmaxc,xMc), + + struct gsm_state * S, + + word * s, /* [0..159] samples IN */ + +/* + * The RPE-LTD coder works on a frame by frame basis. The length of + * the frame is equal to 160 samples. Some computations are done + * once per frame to produce at the output of the coder the + * LARc[1..8] parameters which are the coded LAR coefficients and + * also to realize the inverse filtering operation for the entire + * frame (160 samples of signal d[0..159]). These parts produce at + * the output of the coder: + */ + + word * LARc, /* [0..7] LAR coefficients OUT */ + +/* + * Procedure 4.2.11 to 4.2.18 are to be executed four times per + * frame. That means once for each sub-segment RPE-LTP analysis of + * 40 samples. These parts produce at the output of the coder: + */ + + word * Nc, /* [0..3] LTP lag OUT */ + word * bc, /* [0..3] coded LTP gain OUT */ + word * Mc, /* [0..3] RPE grid selection OUT */ + word * xmaxc,/* [0..3] Coded maximum amplitude OUT */ + word * xMc /* [13*4] normalized RPE samples OUT */ +) +{ + int k; + word * dp = S->dp0 + 120; /* [ -120...-1 ] */ + word * dpp = dp; /* [ 0...39 ] */ + + static word e[50]; + + word so[160]; + + Gsm_Preprocess (S, s, so); + Gsm_LPC_Analysis (S, so, LARc); + Gsm_Short_Term_Analysis_Filter (S, LARc, so); + + for (k = 0; k <= 3; k++, xMc += 13) { + + Gsm_Long_Term_Predictor ( S, + so+k*40, /* d [0..39] IN */ + dp, /* dp [-120..-1] IN */ + e + 5, /* e [0..39] OUT */ + dpp, /* dpp [0..39] OUT */ + Nc++, + bc++); + + Gsm_RPE_Encoding ( S, + e + 5, /* e ][0..39][ IN/OUT */ + xmaxc++, Mc++, xMc ); + /* + * Gsm_Update_of_reconstructed_short_time_residual_signal + * ( dpp, e + 5, dp ); + */ + + { register int i; + register longword ltmp; + for (i = 0; i <= 39; i++) + dp[ i ] = GSM_ADD( e[5 + i], dpp[i] ); + } + dp += 40; + dpp += 40; + + } + (void)memcpy( (char *)S->dp0, (char *)(S->dp0 + 160), + 120 * sizeof(*S->dp0) ); +} diff --git a/src/audio/gsm/src/debug.cpp b/src/audio/gsm/src/debug.cpp new file mode 100644 index 0000000..04c3907 --- /dev/null +++ b/src/audio/gsm/src/debug.cpp @@ -0,0 +1,76 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/* $Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/debug.c,v 1.2 1993/01/29 18:22:20 jutta Exp $ */ + +#include "private.h" + +#ifndef NDEBUG + +/* If NDEBUG _is_ defined and no debugging should be performed, + * calls to functions in this module are #defined to nothing + * in private.h. + */ + +#include +#include "proto.h" + +void gsm_debug_words P4( (name, from, to, ptr), + char * name, + int from, + int to, + word * ptr) +{ + int nprinted = 0; + + fprintf( stderr, "%s [%d .. %d]: ", name, from, to ); + while (from <= to) { + fprintf(stderr, "%d ", ptr[ from ] ); + from++; + if (nprinted++ >= 7) { + nprinted = 0; + if (from < to) putc('\n', stderr); + } + } + putc('\n', stderr); +} + +void gsm_debug_longwords P4( (name, from, to, ptr), + char * name, + int from, + int to, + longword * ptr) +{ + int nprinted = 0; + + fprintf( stderr, "%s [%d .. %d]: ", name, from, to ); + while (from <= to) { + + fprintf(stderr, "%d ", ptr[ from ] ); + from++; + if (nprinted++ >= 7) { + nprinted = 0; + if (from < to) putc('\n', stderr); + } + } + putc('\n', stderr); +} + +void gsm_debug_longword P2( (name, value), + char * name, + longword value ) +{ + fprintf(stderr, "%s: %d\n", name, (long)value ); +} + +void gsm_debug_word P2( (name, value), + char * name, + word value ) +{ + fprintf(stderr, "%s: %d\n", name, (long)value); +} + +#endif diff --git a/src/audio/gsm/src/decode.cpp b/src/audio/gsm/src/decode.cpp new file mode 100644 index 0000000..d51c54a --- /dev/null +++ b/src/audio/gsm/src/decode.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/* $Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/decode.c,v 1.1 1992/10/28 00:15:50 jutta Exp $ */ + +#include + +#include "private.h" +#include "gsm.h" +#include "proto.h" + +/* + * 4.3 FIXED POINT IMPLEMENTATION OF THE RPE-LTP DECODER + */ + +static void Postprocessing P2((S,s), + struct gsm_state * S, + register word * s) +{ + register int k; + register word msr = S->msr; + register longword ltmp; /* for GSM_ADD */ + register word tmp; + + for (k = 160; k--; s++) { + tmp = GSM_MULT_R( msr, 28180 ); + msr = GSM_ADD(*s, tmp); /* Deemphasis */ + *s = GSM_ADD(msr, msr) & 0xFFF8; /* Truncation & Upscaling */ + } + S->msr = msr; +} + +void Gsm_Decoder P8((S,LARcr, Ncr,bcr,Mcr,xmaxcr,xMcr,s), + struct gsm_state * S, + + word * LARcr, /* [0..7] IN */ + + word * Ncr, /* [0..3] IN */ + word * bcr, /* [0..3] IN */ + word * Mcr, /* [0..3] IN */ + word * xmaxcr, /* [0..3] IN */ + word * xMcr, /* [0..13*4] IN */ + + word * s) /* [0..159] OUT */ +{ + int j, k; + word erp[40], wt[160]; + word * drp = S->dp0 + 120; + + for (j=0; j <= 3; j++, xmaxcr++, bcr++, Ncr++, Mcr++, xMcr += 13) { + + Gsm_RPE_Decoding( S, *xmaxcr, *Mcr, xMcr, erp ); + Gsm_Long_Term_Synthesis_Filtering( S, *Ncr, *bcr, erp, drp ); + + for (k = 0; k <= 39; k++) wt[ j * 40 + k ] = drp[ k ]; + } + + Gsm_Short_Term_Synthesis_Filter( S, LARcr, wt, s ); + Postprocessing(S, s); +} diff --git a/src/audio/gsm/src/gsm_create.cpp b/src/audio/gsm/src/gsm_create.cpp new file mode 100644 index 0000000..a0bf634 --- /dev/null +++ b/src/audio/gsm/src/gsm_create.cpp @@ -0,0 +1,45 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +static char const ident[] = "$Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/gsm_create.c,v 1.4 1996/07/02 09:59:05 jutta Exp $"; + +#include "config.h" + +#ifdef HAS_STRING_H +#include +#else +# include "proto.h" + extern char * memset P((char *, int, int)); +#endif + +#ifdef HAS_STDLIB_H +# include +#else +# ifdef HAS_MALLOC_H +# include +# else + extern char * malloc(); +# endif +#endif + +#include + +#include "gsm.h" +#include "private.h" +#include "proto.h" + +gsm gsm_create P0() +{ + gsm r; + + r = (gsm)malloc(sizeof(struct gsm_state)); + if (!r) return r; + + memset((char *)r, 0, sizeof(*r)); + r->nrp = 40; + + return r; +} diff --git a/src/audio/gsm/src/gsm_decode.cpp b/src/audio/gsm/src/gsm_decode.cpp new file mode 100644 index 0000000..973c08b --- /dev/null +++ b/src/audio/gsm/src/gsm_decode.cpp @@ -0,0 +1,361 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/* $Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/gsm_decode.c,v 1.2 1996/07/02 09:59:05 jutta Exp $ */ + +#include "private.h" + +#include "gsm.h" +#include "proto.h" + +int gsm_decode P3((s, c, target), gsm s, gsm_byte * c, gsm_signal * target) +{ + word LARc[8], Nc[4], Mc[4], bc[4], xmaxc[4], xmc[13*4]; + +#ifdef WAV49 + if (s->wav_fmt) { + + uword sr = 0; + + s->frame_index = !s->frame_index; + if (s->frame_index) { + + sr = *c++; + LARc[0] = sr & 0x3f; sr >>= 6; + sr |= (uword)*c++ << 2; + LARc[1] = sr & 0x3f; sr >>= 6; + sr |= (uword)*c++ << 4; + LARc[2] = sr & 0x1f; sr >>= 5; + LARc[3] = sr & 0x1f; sr >>= 5; + sr |= (uword)*c++ << 2; + LARc[4] = sr & 0xf; sr >>= 4; + LARc[5] = sr & 0xf; sr >>= 4; + sr |= (uword)*c++ << 2; /* 5 */ + LARc[6] = sr & 0x7; sr >>= 3; + LARc[7] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 4; + Nc[0] = sr & 0x7f; sr >>= 7; + bc[0] = sr & 0x3; sr >>= 2; + Mc[0] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 1; + xmaxc[0] = sr & 0x3f; sr >>= 6; + xmc[0] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[1] = sr & 0x7; sr >>= 3; + xmc[2] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[3] = sr & 0x7; sr >>= 3; + xmc[4] = sr & 0x7; sr >>= 3; + xmc[5] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; /* 10 */ + xmc[6] = sr & 0x7; sr >>= 3; + xmc[7] = sr & 0x7; sr >>= 3; + xmc[8] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[9] = sr & 0x7; sr >>= 3; + xmc[10] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[11] = sr & 0x7; sr >>= 3; + xmc[12] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 4; + Nc[1] = sr & 0x7f; sr >>= 7; + bc[1] = sr & 0x3; sr >>= 2; + Mc[1] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 1; + xmaxc[1] = sr & 0x3f; sr >>= 6; + xmc[13] = sr & 0x7; sr >>= 3; + sr = *c++; /* 15 */ + xmc[14] = sr & 0x7; sr >>= 3; + xmc[15] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[16] = sr & 0x7; sr >>= 3; + xmc[17] = sr & 0x7; sr >>= 3; + xmc[18] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[19] = sr & 0x7; sr >>= 3; + xmc[20] = sr & 0x7; sr >>= 3; + xmc[21] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[22] = sr & 0x7; sr >>= 3; + xmc[23] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[24] = sr & 0x7; sr >>= 3; + xmc[25] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 4; /* 20 */ + Nc[2] = sr & 0x7f; sr >>= 7; + bc[2] = sr & 0x3; sr >>= 2; + Mc[2] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 1; + xmaxc[2] = sr & 0x3f; sr >>= 6; + xmc[26] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[27] = sr & 0x7; sr >>= 3; + xmc[28] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[29] = sr & 0x7; sr >>= 3; + xmc[30] = sr & 0x7; sr >>= 3; + xmc[31] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[32] = sr & 0x7; sr >>= 3; + xmc[33] = sr & 0x7; sr >>= 3; + xmc[34] = sr & 0x7; sr >>= 3; + sr = *c++; /* 25 */ + xmc[35] = sr & 0x7; sr >>= 3; + xmc[36] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[37] = sr & 0x7; sr >>= 3; + xmc[38] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 4; + Nc[3] = sr & 0x7f; sr >>= 7; + bc[3] = sr & 0x3; sr >>= 2; + Mc[3] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 1; + xmaxc[3] = sr & 0x3f; sr >>= 6; + xmc[39] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[40] = sr & 0x7; sr >>= 3; + xmc[41] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; /* 30 */ + xmc[42] = sr & 0x7; sr >>= 3; + xmc[43] = sr & 0x7; sr >>= 3; + xmc[44] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[45] = sr & 0x7; sr >>= 3; + xmc[46] = sr & 0x7; sr >>= 3; + xmc[47] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[48] = sr & 0x7; sr >>= 3; + xmc[49] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[50] = sr & 0x7; sr >>= 3; + xmc[51] = sr & 0x7; sr >>= 3; + + s->frame_chain = sr & 0xf; + } + else { + sr = s->frame_chain; + sr |= (uword)*c++ << 4; /* 1 */ + LARc[0] = sr & 0x3f; sr >>= 6; + LARc[1] = sr & 0x3f; sr >>= 6; + sr = *c++; + LARc[2] = sr & 0x1f; sr >>= 5; + sr |= (uword)*c++ << 3; + LARc[3] = sr & 0x1f; sr >>= 5; + LARc[4] = sr & 0xf; sr >>= 4; + sr |= (uword)*c++ << 2; + LARc[5] = sr & 0xf; sr >>= 4; + LARc[6] = sr & 0x7; sr >>= 3; + LARc[7] = sr & 0x7; sr >>= 3; + sr = *c++; /* 5 */ + Nc[0] = sr & 0x7f; sr >>= 7; + sr |= (uword)*c++ << 1; + bc[0] = sr & 0x3; sr >>= 2; + Mc[0] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 5; + xmaxc[0] = sr & 0x3f; sr >>= 6; + xmc[0] = sr & 0x7; sr >>= 3; + xmc[1] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[2] = sr & 0x7; sr >>= 3; + xmc[3] = sr & 0x7; sr >>= 3; + xmc[4] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[5] = sr & 0x7; sr >>= 3; + xmc[6] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; /* 10 */ + xmc[7] = sr & 0x7; sr >>= 3; + xmc[8] = sr & 0x7; sr >>= 3; + xmc[9] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[10] = sr & 0x7; sr >>= 3; + xmc[11] = sr & 0x7; sr >>= 3; + xmc[12] = sr & 0x7; sr >>= 3; + sr = *c++; + Nc[1] = sr & 0x7f; sr >>= 7; + sr |= (uword)*c++ << 1; + bc[1] = sr & 0x3; sr >>= 2; + Mc[1] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 5; + xmaxc[1] = sr & 0x3f; sr >>= 6; + xmc[13] = sr & 0x7; sr >>= 3; + xmc[14] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; /* 15 */ + xmc[15] = sr & 0x7; sr >>= 3; + xmc[16] = sr & 0x7; sr >>= 3; + xmc[17] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[18] = sr & 0x7; sr >>= 3; + xmc[19] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[20] = sr & 0x7; sr >>= 3; + xmc[21] = sr & 0x7; sr >>= 3; + xmc[22] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[23] = sr & 0x7; sr >>= 3; + xmc[24] = sr & 0x7; sr >>= 3; + xmc[25] = sr & 0x7; sr >>= 3; + sr = *c++; + Nc[2] = sr & 0x7f; sr >>= 7; + sr |= (uword)*c++ << 1; /* 20 */ + bc[2] = sr & 0x3; sr >>= 2; + Mc[2] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 5; + xmaxc[2] = sr & 0x3f; sr >>= 6; + xmc[26] = sr & 0x7; sr >>= 3; + xmc[27] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[28] = sr & 0x7; sr >>= 3; + xmc[29] = sr & 0x7; sr >>= 3; + xmc[30] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[31] = sr & 0x7; sr >>= 3; + xmc[32] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[33] = sr & 0x7; sr >>= 3; + xmc[34] = sr & 0x7; sr >>= 3; + xmc[35] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; /* 25 */ + xmc[36] = sr & 0x7; sr >>= 3; + xmc[37] = sr & 0x7; sr >>= 3; + xmc[38] = sr & 0x7; sr >>= 3; + sr = *c++; + Nc[3] = sr & 0x7f; sr >>= 7; + sr |= (uword)*c++ << 1; + bc[3] = sr & 0x3; sr >>= 2; + Mc[3] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 5; + xmaxc[3] = sr & 0x3f; sr >>= 6; + xmc[39] = sr & 0x7; sr >>= 3; + xmc[40] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[41] = sr & 0x7; sr >>= 3; + xmc[42] = sr & 0x7; sr >>= 3; + xmc[43] = sr & 0x7; sr >>= 3; + sr = *c++; /* 30 */ + xmc[44] = sr & 0x7; sr >>= 3; + xmc[45] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[46] = sr & 0x7; sr >>= 3; + xmc[47] = sr & 0x7; sr >>= 3; + xmc[48] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[49] = sr & 0x7; sr >>= 3; + xmc[50] = sr & 0x7; sr >>= 3; + xmc[51] = sr & 0x7; sr >>= 3; + } + } + else +#endif + { + /* GSM_MAGIC = (*c >> 4) & 0xF; */ + + if (((*c >> 4) & 0x0F) != GSM_MAGIC) return -1; + + LARc[0] = (*c++ & 0xF) << 2; /* 1 */ + LARc[0] |= (*c >> 6) & 0x3; + LARc[1] = *c++ & 0x3F; + LARc[2] = (*c >> 3) & 0x1F; + LARc[3] = (*c++ & 0x7) << 2; + LARc[3] |= (*c >> 6) & 0x3; + LARc[4] = (*c >> 2) & 0xF; + LARc[5] = (*c++ & 0x3) << 2; + LARc[5] |= (*c >> 6) & 0x3; + LARc[6] = (*c >> 3) & 0x7; + LARc[7] = *c++ & 0x7; + Nc[0] = (*c >> 1) & 0x7F; + bc[0] = (*c++ & 0x1) << 1; + bc[0] |= (*c >> 7) & 0x1; + Mc[0] = (*c >> 5) & 0x3; + xmaxc[0] = (*c++ & 0x1F) << 1; + xmaxc[0] |= (*c >> 7) & 0x1; + xmc[0] = (*c >> 4) & 0x7; + xmc[1] = (*c >> 1) & 0x7; + xmc[2] = (*c++ & 0x1) << 2; + xmc[2] |= (*c >> 6) & 0x3; + xmc[3] = (*c >> 3) & 0x7; + xmc[4] = *c++ & 0x7; + xmc[5] = (*c >> 5) & 0x7; + xmc[6] = (*c >> 2) & 0x7; + xmc[7] = (*c++ & 0x3) << 1; /* 10 */ + xmc[7] |= (*c >> 7) & 0x1; + xmc[8] = (*c >> 4) & 0x7; + xmc[9] = (*c >> 1) & 0x7; + xmc[10] = (*c++ & 0x1) << 2; + xmc[10] |= (*c >> 6) & 0x3; + xmc[11] = (*c >> 3) & 0x7; + xmc[12] = *c++ & 0x7; + Nc[1] = (*c >> 1) & 0x7F; + bc[1] = (*c++ & 0x1) << 1; + bc[1] |= (*c >> 7) & 0x1; + Mc[1] = (*c >> 5) & 0x3; + xmaxc[1] = (*c++ & 0x1F) << 1; + xmaxc[1] |= (*c >> 7) & 0x1; + xmc[13] = (*c >> 4) & 0x7; + xmc[14] = (*c >> 1) & 0x7; + xmc[15] = (*c++ & 0x1) << 2; + xmc[15] |= (*c >> 6) & 0x3; + xmc[16] = (*c >> 3) & 0x7; + xmc[17] = *c++ & 0x7; + xmc[18] = (*c >> 5) & 0x7; + xmc[19] = (*c >> 2) & 0x7; + xmc[20] = (*c++ & 0x3) << 1; + xmc[20] |= (*c >> 7) & 0x1; + xmc[21] = (*c >> 4) & 0x7; + xmc[22] = (*c >> 1) & 0x7; + xmc[23] = (*c++ & 0x1) << 2; + xmc[23] |= (*c >> 6) & 0x3; + xmc[24] = (*c >> 3) & 0x7; + xmc[25] = *c++ & 0x7; + Nc[2] = (*c >> 1) & 0x7F; + bc[2] = (*c++ & 0x1) << 1; /* 20 */ + bc[2] |= (*c >> 7) & 0x1; + Mc[2] = (*c >> 5) & 0x3; + xmaxc[2] = (*c++ & 0x1F) << 1; + xmaxc[2] |= (*c >> 7) & 0x1; + xmc[26] = (*c >> 4) & 0x7; + xmc[27] = (*c >> 1) & 0x7; + xmc[28] = (*c++ & 0x1) << 2; + xmc[28] |= (*c >> 6) & 0x3; + xmc[29] = (*c >> 3) & 0x7; + xmc[30] = *c++ & 0x7; + xmc[31] = (*c >> 5) & 0x7; + xmc[32] = (*c >> 2) & 0x7; + xmc[33] = (*c++ & 0x3) << 1; + xmc[33] |= (*c >> 7) & 0x1; + xmc[34] = (*c >> 4) & 0x7; + xmc[35] = (*c >> 1) & 0x7; + xmc[36] = (*c++ & 0x1) << 2; + xmc[36] |= (*c >> 6) & 0x3; + xmc[37] = (*c >> 3) & 0x7; + xmc[38] = *c++ & 0x7; + Nc[3] = (*c >> 1) & 0x7F; + bc[3] = (*c++ & 0x1) << 1; + bc[3] |= (*c >> 7) & 0x1; + Mc[3] = (*c >> 5) & 0x3; + xmaxc[3] = (*c++ & 0x1F) << 1; + xmaxc[3] |= (*c >> 7) & 0x1; + xmc[39] = (*c >> 4) & 0x7; + xmc[40] = (*c >> 1) & 0x7; + xmc[41] = (*c++ & 0x1) << 2; + xmc[41] |= (*c >> 6) & 0x3; + xmc[42] = (*c >> 3) & 0x7; + xmc[43] = *c++ & 0x7; /* 30 */ + xmc[44] = (*c >> 5) & 0x7; + xmc[45] = (*c >> 2) & 0x7; + xmc[46] = (*c++ & 0x3) << 1; + xmc[46] |= (*c >> 7) & 0x1; + xmc[47] = (*c >> 4) & 0x7; + xmc[48] = (*c >> 1) & 0x7; + xmc[49] = (*c++ & 0x1) << 2; + xmc[49] |= (*c >> 6) & 0x3; + xmc[50] = (*c >> 3) & 0x7; + xmc[51] = *c & 0x7; /* 33 */ + } + + Gsm_Decoder(s, LARc, Nc, bc, Mc, xmaxc, xmc, target); + + return 0; +} diff --git a/src/audio/gsm/src/gsm_destroy.cpp b/src/audio/gsm/src/gsm_destroy.cpp new file mode 100644 index 0000000..03c8659 --- /dev/null +++ b/src/audio/gsm/src/gsm_destroy.cpp @@ -0,0 +1,26 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/* $Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/gsm_destroy.c,v 1.3 1994/11/28 19:52:25 jutta Exp $ */ + +#include "gsm.h" +#include "config.h" +#include "proto.h" + +#ifdef HAS_STDLIB_H +# include +#else +# ifdef HAS_MALLOC_H +# include +# else + extern void free(); +# endif +#endif + +void gsm_destroy P1((S), gsm S) +{ + if (S) free((char *)S); +} diff --git a/src/audio/gsm/src/gsm_encode.cpp b/src/audio/gsm/src/gsm_encode.cpp new file mode 100644 index 0000000..51bc556 --- /dev/null +++ b/src/audio/gsm/src/gsm_encode.cpp @@ -0,0 +1,451 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/* $Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/gsm_encode.c,v 1.2 1996/07/02 09:59:05 jutta Exp $ */ + +#include "private.h" +#include "gsm.h" +#include "proto.h" + +void gsm_encode P3((s, source, c), gsm s, gsm_signal * source, gsm_byte * c) +{ + word LARc[8], Nc[4], Mc[4], bc[4], xmaxc[4], xmc[13*4]; + + Gsm_Coder(s, source, LARc, Nc, bc, Mc, xmaxc, xmc); + + + /* variable size + + GSM_MAGIC 4 + + LARc[0] 6 + LARc[1] 6 + LARc[2] 5 + LARc[3] 5 + LARc[4] 4 + LARc[5] 4 + LARc[6] 3 + LARc[7] 3 + + Nc[0] 7 + bc[0] 2 + Mc[0] 2 + xmaxc[0] 6 + xmc[0] 3 + xmc[1] 3 + xmc[2] 3 + xmc[3] 3 + xmc[4] 3 + xmc[5] 3 + xmc[6] 3 + xmc[7] 3 + xmc[8] 3 + xmc[9] 3 + xmc[10] 3 + xmc[11] 3 + xmc[12] 3 + + Nc[1] 7 + bc[1] 2 + Mc[1] 2 + xmaxc[1] 6 + xmc[13] 3 + xmc[14] 3 + xmc[15] 3 + xmc[16] 3 + xmc[17] 3 + xmc[18] 3 + xmc[19] 3 + xmc[20] 3 + xmc[21] 3 + xmc[22] 3 + xmc[23] 3 + xmc[24] 3 + xmc[25] 3 + + Nc[2] 7 + bc[2] 2 + Mc[2] 2 + xmaxc[2] 6 + xmc[26] 3 + xmc[27] 3 + xmc[28] 3 + xmc[29] 3 + xmc[30] 3 + xmc[31] 3 + xmc[32] 3 + xmc[33] 3 + xmc[34] 3 + xmc[35] 3 + xmc[36] 3 + xmc[37] 3 + xmc[38] 3 + + Nc[3] 7 + bc[3] 2 + Mc[3] 2 + xmaxc[3] 6 + xmc[39] 3 + xmc[40] 3 + xmc[41] 3 + xmc[42] 3 + xmc[43] 3 + xmc[44] 3 + xmc[45] 3 + xmc[46] 3 + xmc[47] 3 + xmc[48] 3 + xmc[49] 3 + xmc[50] 3 + xmc[51] 3 + */ + +#ifdef WAV49 + + if (s->wav_fmt) { + s->frame_index = !s->frame_index; + if (s->frame_index) { + + uword sr; + + sr = 0; + sr = sr >> 6 | LARc[0] << 10; + sr = sr >> 6 | LARc[1] << 10; + *c++ = sr >> 4; + sr = sr >> 5 | LARc[2] << 11; + *c++ = sr >> 7; + sr = sr >> 5 | LARc[3] << 11; + sr = sr >> 4 | LARc[4] << 12; + *c++ = sr >> 6; + sr = sr >> 4 | LARc[5] << 12; + sr = sr >> 3 | LARc[6] << 13; + *c++ = sr >> 7; + sr = sr >> 3 | LARc[7] << 13; + sr = sr >> 7 | Nc[0] << 9; + *c++ = sr >> 5; + sr = sr >> 2 | bc[0] << 14; + sr = sr >> 2 | Mc[0] << 14; + sr = sr >> 6 | xmaxc[0] << 10; + *c++ = sr >> 3; + sr = sr >> 3 | xmc[0] << 13; + *c++ = sr >> 8; + sr = sr >> 3 | xmc[1] << 13; + sr = sr >> 3 | xmc[2] << 13; + sr = sr >> 3 | xmc[3] << 13; + *c++ = sr >> 7; + sr = sr >> 3 | xmc[4] << 13; + sr = sr >> 3 | xmc[5] << 13; + sr = sr >> 3 | xmc[6] << 13; + *c++ = sr >> 6; + sr = sr >> 3 | xmc[7] << 13; + sr = sr >> 3 | xmc[8] << 13; + *c++ = sr >> 8; + sr = sr >> 3 | xmc[9] << 13; + sr = sr >> 3 | xmc[10] << 13; + sr = sr >> 3 | xmc[11] << 13; + *c++ = sr >> 7; + sr = sr >> 3 | xmc[12] << 13; + sr = sr >> 7 | Nc[1] << 9; + *c++ = sr >> 5; + sr = sr >> 2 | bc[1] << 14; + sr = sr >> 2 | Mc[1] << 14; + sr = sr >> 6 | xmaxc[1] << 10; + *c++ = sr >> 3; + sr = sr >> 3 | xmc[13] << 13; + *c++ = sr >> 8; + sr = sr >> 3 | xmc[14] << 13; + sr = sr >> 3 | xmc[15] << 13; + sr = sr >> 3 | xmc[16] << 13; + *c++ = sr >> 7; + sr = sr >> 3 | xmc[17] << 13; + sr = sr >> 3 | xmc[18] << 13; + sr = sr >> 3 | xmc[19] << 13; + *c++ = sr >> 6; + sr = sr >> 3 | xmc[20] << 13; + sr = sr >> 3 | xmc[21] << 13; + *c++ = sr >> 8; + sr = sr >> 3 | xmc[22] << 13; + sr = sr >> 3 | xmc[23] << 13; + sr = sr >> 3 | xmc[24] << 13; + *c++ = sr >> 7; + sr = sr >> 3 | xmc[25] << 13; + sr = sr >> 7 | Nc[2] << 9; + *c++ = sr >> 5; + sr = sr >> 2 | bc[2] << 14; + sr = sr >> 2 | Mc[2] << 14; + sr = sr >> 6 | xmaxc[2] << 10; + *c++ = sr >> 3; + sr = sr >> 3 | xmc[26] << 13; + *c++ = sr >> 8; + sr = sr >> 3 | xmc[27] << 13; + sr = sr >> 3 | xmc[28] << 13; + sr = sr >> 3 | xmc[29] << 13; + *c++ = sr >> 7; + sr = sr >> 3 | xmc[30] << 13; + sr = sr >> 3 | xmc[31] << 13; + sr = sr >> 3 | xmc[32] << 13; + *c++ = sr >> 6; + sr = sr >> 3 | xmc[33] << 13; + sr = sr >> 3 | xmc[34] << 13; + *c++ = sr >> 8; + sr = sr >> 3 | xmc[35] << 13; + sr = sr >> 3 | xmc[36] << 13; + sr = sr >> 3 | xmc[37] << 13; + *c++ = sr >> 7; + sr = sr >> 3 | xmc[38] << 13; + sr = sr >> 7 | Nc[3] << 9; + *c++ = sr >> 5; + sr = sr >> 2 | bc[3] << 14; + sr = sr >> 2 | Mc[3] << 14; + sr = sr >> 6 | xmaxc[3] << 10; + *c++ = sr >> 3; + sr = sr >> 3 | xmc[39] << 13; + *c++ = sr >> 8; + sr = sr >> 3 | xmc[40] << 13; + sr = sr >> 3 | xmc[41] << 13; + sr = sr >> 3 | xmc[42] << 13; + *c++ = sr >> 7; + sr = sr >> 3 | xmc[43] << 13; + sr = sr >> 3 | xmc[44] << 13; + sr = sr >> 3 | xmc[45] << 13; + *c++ = sr >> 6; + sr = sr >> 3 | xmc[46] << 13; + sr = sr >> 3 | xmc[47] << 13; + *c++ = sr >> 8; + sr = sr >> 3 | xmc[48] << 13; + sr = sr >> 3 | xmc[49] << 13; + sr = sr >> 3 | xmc[50] << 13; + *c++ = sr >> 7; + sr = sr >> 3 | xmc[51] << 13; + sr = sr >> 4; + *c = sr >> 8; + s->frame_chain = *c; + } + else { + uword sr; + + sr = 0; + sr = sr >> 4 | s->frame_chain << 12; + sr = sr >> 6 | LARc[0] << 10; + *c++ = sr >> 6; + sr = sr >> 6 | LARc[1] << 10; + *c++ = sr >> 8; + sr = sr >> 5 | LARc[2] << 11; + sr = sr >> 5 | LARc[3] << 11; + *c++ = sr >> 6; + sr = sr >> 4 | LARc[4] << 12; + sr = sr >> 4 | LARc[5] << 12; + *c++ = sr >> 6; + sr = sr >> 3 | LARc[6] << 13; + sr = sr >> 3 | LARc[7] << 13; + *c++ = sr >> 8; + sr = sr >> 7 | Nc[0] << 9; + sr = sr >> 2 | bc[0] << 14; + *c++ = sr >> 7; + sr = sr >> 2 | Mc[0] << 14; + sr = sr >> 6 | xmaxc[0] << 10; + *c++ = sr >> 7; + sr = sr >> 3 | xmc[0] << 13; + sr = sr >> 3 | xmc[1] << 13; + sr = sr >> 3 | xmc[2] << 13; + *c++ = sr >> 6; + sr = sr >> 3 | xmc[3] << 13; + sr = sr >> 3 | xmc[4] << 13; + *c++ = sr >> 8; + sr = sr >> 3 | xmc[5] << 13; + sr = sr >> 3 | xmc[6] << 13; + sr = sr >> 3 | xmc[7] << 13; + *c++ = sr >> 7; + sr = sr >> 3 | xmc[8] << 13; + sr = sr >> 3 | xmc[9] << 13; + sr = sr >> 3 | xmc[10] << 13; + *c++ = sr >> 6; + sr = sr >> 3 | xmc[11] << 13; + sr = sr >> 3 | xmc[12] << 13; + *c++ = sr >> 8; + sr = sr >> 7 | Nc[1] << 9; + sr = sr >> 2 | bc[1] << 14; + *c++ = sr >> 7; + sr = sr >> 2 | Mc[1] << 14; + sr = sr >> 6 | xmaxc[1] << 10; + *c++ = sr >> 7; + sr = sr >> 3 | xmc[13] << 13; + sr = sr >> 3 | xmc[14] << 13; + sr = sr >> 3 | xmc[15] << 13; + *c++ = sr >> 6; + sr = sr >> 3 | xmc[16] << 13; + sr = sr >> 3 | xmc[17] << 13; + *c++ = sr >> 8; + sr = sr >> 3 | xmc[18] << 13; + sr = sr >> 3 | xmc[19] << 13; + sr = sr >> 3 | xmc[20] << 13; + *c++ = sr >> 7; + sr = sr >> 3 | xmc[21] << 13; + sr = sr >> 3 | xmc[22] << 13; + sr = sr >> 3 | xmc[23] << 13; + *c++ = sr >> 6; + sr = sr >> 3 | xmc[24] << 13; + sr = sr >> 3 | xmc[25] << 13; + *c++ = sr >> 8; + sr = sr >> 7 | Nc[2] << 9; + sr = sr >> 2 | bc[2] << 14; + *c++ = sr >> 7; + sr = sr >> 2 | Mc[2] << 14; + sr = sr >> 6 | xmaxc[2] << 10; + *c++ = sr >> 7; + sr = sr >> 3 | xmc[26] << 13; + sr = sr >> 3 | xmc[27] << 13; + sr = sr >> 3 | xmc[28] << 13; + *c++ = sr >> 6; + sr = sr >> 3 | xmc[29] << 13; + sr = sr >> 3 | xmc[30] << 13; + *c++ = sr >> 8; + sr = sr >> 3 | xmc[31] << 13; + sr = sr >> 3 | xmc[32] << 13; + sr = sr >> 3 | xmc[33] << 13; + *c++ = sr >> 7; + sr = sr >> 3 | xmc[34] << 13; + sr = sr >> 3 | xmc[35] << 13; + sr = sr >> 3 | xmc[36] << 13; + *c++ = sr >> 6; + sr = sr >> 3 | xmc[37] << 13; + sr = sr >> 3 | xmc[38] << 13; + *c++ = sr >> 8; + sr = sr >> 7 | Nc[3] << 9; + sr = sr >> 2 | bc[3] << 14; + *c++ = sr >> 7; + sr = sr >> 2 | Mc[3] << 14; + sr = sr >> 6 | xmaxc[3] << 10; + *c++ = sr >> 7; + sr = sr >> 3 | xmc[39] << 13; + sr = sr >> 3 | xmc[40] << 13; + sr = sr >> 3 | xmc[41] << 13; + *c++ = sr >> 6; + sr = sr >> 3 | xmc[42] << 13; + sr = sr >> 3 | xmc[43] << 13; + *c++ = sr >> 8; + sr = sr >> 3 | xmc[44] << 13; + sr = sr >> 3 | xmc[45] << 13; + sr = sr >> 3 | xmc[46] << 13; + *c++ = sr >> 7; + sr = sr >> 3 | xmc[47] << 13; + sr = sr >> 3 | xmc[48] << 13; + sr = sr >> 3 | xmc[49] << 13; + *c++ = sr >> 6; + sr = sr >> 3 | xmc[50] << 13; + sr = sr >> 3 | xmc[51] << 13; + *c++ = sr >> 8; + } + } + + else + +#endif /* WAV49 */ + { + + *c++ = ((GSM_MAGIC & 0xF) << 4) /* 1 */ + | ((LARc[0] >> 2) & 0xF); + *c++ = ((LARc[0] & 0x3) << 6) + | (LARc[1] & 0x3F); + *c++ = ((LARc[2] & 0x1F) << 3) + | ((LARc[3] >> 2) & 0x7); + *c++ = ((LARc[3] & 0x3) << 6) + | ((LARc[4] & 0xF) << 2) + | ((LARc[5] >> 2) & 0x3); + *c++ = ((LARc[5] & 0x3) << 6) + | ((LARc[6] & 0x7) << 3) + | (LARc[7] & 0x7); + *c++ = ((Nc[0] & 0x7F) << 1) + | ((bc[0] >> 1) & 0x1); + *c++ = ((bc[0] & 0x1) << 7) + | ((Mc[0] & 0x3) << 5) + | ((xmaxc[0] >> 1) & 0x1F); + *c++ = ((xmaxc[0] & 0x1) << 7) + | ((xmc[0] & 0x7) << 4) + | ((xmc[1] & 0x7) << 1) + | ((xmc[2] >> 2) & 0x1); + *c++ = ((xmc[2] & 0x3) << 6) + | ((xmc[3] & 0x7) << 3) + | (xmc[4] & 0x7); + *c++ = ((xmc[5] & 0x7) << 5) /* 10 */ + | ((xmc[6] & 0x7) << 2) + | ((xmc[7] >> 1) & 0x3); + *c++ = ((xmc[7] & 0x1) << 7) + | ((xmc[8] & 0x7) << 4) + | ((xmc[9] & 0x7) << 1) + | ((xmc[10] >> 2) & 0x1); + *c++ = ((xmc[10] & 0x3) << 6) + | ((xmc[11] & 0x7) << 3) + | (xmc[12] & 0x7); + *c++ = ((Nc[1] & 0x7F) << 1) + | ((bc[1] >> 1) & 0x1); + *c++ = ((bc[1] & 0x1) << 7) + | ((Mc[1] & 0x3) << 5) + | ((xmaxc[1] >> 1) & 0x1F); + *c++ = ((xmaxc[1] & 0x1) << 7) + | ((xmc[13] & 0x7) << 4) + | ((xmc[14] & 0x7) << 1) + | ((xmc[15] >> 2) & 0x1); + *c++ = ((xmc[15] & 0x3) << 6) + | ((xmc[16] & 0x7) << 3) + | (xmc[17] & 0x7); + *c++ = ((xmc[18] & 0x7) << 5) + | ((xmc[19] & 0x7) << 2) + | ((xmc[20] >> 1) & 0x3); + *c++ = ((xmc[20] & 0x1) << 7) + | ((xmc[21] & 0x7) << 4) + | ((xmc[22] & 0x7) << 1) + | ((xmc[23] >> 2) & 0x1); + *c++ = ((xmc[23] & 0x3) << 6) + | ((xmc[24] & 0x7) << 3) + | (xmc[25] & 0x7); + *c++ = ((Nc[2] & 0x7F) << 1) /* 20 */ + | ((bc[2] >> 1) & 0x1); + *c++ = ((bc[2] & 0x1) << 7) + | ((Mc[2] & 0x3) << 5) + | ((xmaxc[2] >> 1) & 0x1F); + *c++ = ((xmaxc[2] & 0x1) << 7) + | ((xmc[26] & 0x7) << 4) + | ((xmc[27] & 0x7) << 1) + | ((xmc[28] >> 2) & 0x1); + *c++ = ((xmc[28] & 0x3) << 6) + | ((xmc[29] & 0x7) << 3) + | (xmc[30] & 0x7); + *c++ = ((xmc[31] & 0x7) << 5) + | ((xmc[32] & 0x7) << 2) + | ((xmc[33] >> 1) & 0x3); + *c++ = ((xmc[33] & 0x1) << 7) + | ((xmc[34] & 0x7) << 4) + | ((xmc[35] & 0x7) << 1) + | ((xmc[36] >> 2) & 0x1); + *c++ = ((xmc[36] & 0x3) << 6) + | ((xmc[37] & 0x7) << 3) + | (xmc[38] & 0x7); + *c++ = ((Nc[3] & 0x7F) << 1) + | ((bc[3] >> 1) & 0x1); + *c++ = ((bc[3] & 0x1) << 7) + | ((Mc[3] & 0x3) << 5) + | ((xmaxc[3] >> 1) & 0x1F); + *c++ = ((xmaxc[3] & 0x1) << 7) + | ((xmc[39] & 0x7) << 4) + | ((xmc[40] & 0x7) << 1) + | ((xmc[41] >> 2) & 0x1); + *c++ = ((xmc[41] & 0x3) << 6) /* 30 */ + | ((xmc[42] & 0x7) << 3) + | (xmc[43] & 0x7); + *c++ = ((xmc[44] & 0x7) << 5) + | ((xmc[45] & 0x7) << 2) + | ((xmc[46] >> 1) & 0x3); + *c++ = ((xmc[46] & 0x1) << 7) + | ((xmc[47] & 0x7) << 4) + | ((xmc[48] & 0x7) << 1) + | ((xmc[49] >> 2) & 0x1); + *c++ = ((xmc[49] & 0x3) << 6) + | ((xmc[50] & 0x7) << 3) + | (xmc[51] & 0x7); + + } +} diff --git a/src/audio/gsm/src/gsm_explode.cpp b/src/audio/gsm/src/gsm_explode.cpp new file mode 100644 index 0000000..ca48f9b --- /dev/null +++ b/src/audio/gsm/src/gsm_explode.cpp @@ -0,0 +1,417 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/* $Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/gsm_explode.c,v 1.2 1996/07/02 14:32:42 jutta Exp jutta $ */ + +#include "private.h" +#include "gsm.h" +#include "proto.h" + +int gsm_explode P3((s, c, target), gsm s, gsm_byte * c, gsm_signal * target) +{ +# define LARc target +# define Nc *((gsm_signal (*) [17])(target + 8)) +# define bc *((gsm_signal (*) [17])(target + 9)) +# define Mc *((gsm_signal (*) [17])(target + 10)) +# define xmaxc *((gsm_signal (*) [17])(target + 11)) + + +#ifdef WAV49 + if (s->wav_fmt) { + + uword sr = 0; + + if (s->frame_index == 1) { + + sr = *c++; + LARc[0] = sr & 0x3f; sr >>= 6; + sr |= (uword)*c++ << 2; + LARc[1] = sr & 0x3f; sr >>= 6; + sr |= (uword)*c++ << 4; + LARc[2] = sr & 0x1f; sr >>= 5; + LARc[3] = sr & 0x1f; sr >>= 5; + sr |= (uword)*c++ << 2; + LARc[4] = sr & 0xf; sr >>= 4; + LARc[5] = sr & 0xf; sr >>= 4; + sr |= (uword)*c++ << 2; /* 5 */ + LARc[6] = sr & 0x7; sr >>= 3; + LARc[7] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 4; + Nc[0] = sr & 0x7f; sr >>= 7; + bc[0] = sr & 0x3; sr >>= 2; + Mc[0] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 1; + xmaxc[0] = sr & 0x3f; sr >>= 6; +#undef xmc +#define xmc (target + 12) + xmc[0] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[1] = sr & 0x7; sr >>= 3; + xmc[2] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[3] = sr & 0x7; sr >>= 3; + xmc[4] = sr & 0x7; sr >>= 3; + xmc[5] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; /* 10 */ + xmc[6] = sr & 0x7; sr >>= 3; + xmc[7] = sr & 0x7; sr >>= 3; + xmc[8] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[9] = sr & 0x7; sr >>= 3; + xmc[10] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[11] = sr & 0x7; sr >>= 3; + xmc[12] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 4; + Nc[1] = sr & 0x7f; sr >>= 7; + bc[1] = sr & 0x3; sr >>= 2; + Mc[1] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 1; + xmaxc[1] = sr & 0x3f; sr >>= 6; +#undef xmc +#define xmc (target + 29 - 13) + + xmc[13] = sr & 0x7; sr >>= 3; + sr = *c++; /* 15 */ + xmc[14] = sr & 0x7; sr >>= 3; + xmc[15] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[16] = sr & 0x7; sr >>= 3; + xmc[17] = sr & 0x7; sr >>= 3; + xmc[18] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[19] = sr & 0x7; sr >>= 3; + xmc[20] = sr & 0x7; sr >>= 3; + xmc[21] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[22] = sr & 0x7; sr >>= 3; + xmc[23] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[24] = sr & 0x7; sr >>= 3; + xmc[25] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 4; /* 20 */ + Nc[2] = sr & 0x7f; sr >>= 7; + bc[2] = sr & 0x3; sr >>= 2; + Mc[2] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 1; + xmaxc[2] = sr & 0x3f; sr >>= 6; + +#undef xmc +#define xmc (target + 46 - 26) + + xmc[26] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[27] = sr & 0x7; sr >>= 3; + xmc[28] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[29] = sr & 0x7; sr >>= 3; + xmc[30] = sr & 0x7; sr >>= 3; + xmc[31] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[32] = sr & 0x7; sr >>= 3; + xmc[33] = sr & 0x7; sr >>= 3; + xmc[34] = sr & 0x7; sr >>= 3; + sr = *c++; /* 25 */ + xmc[35] = sr & 0x7; sr >>= 3; + xmc[36] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[37] = sr & 0x7; sr >>= 3; + xmc[38] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 4; + Nc[3] = sr & 0x7f; sr >>= 7; + bc[3] = sr & 0x3; sr >>= 2; + Mc[3] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 1; + xmaxc[3] = sr & 0x3f; sr >>= 6; +#undef xmc +#define xmc (target + 63 - 39) + + xmc[39] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[40] = sr & 0x7; sr >>= 3; + xmc[41] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; /* 30 */ + xmc[42] = sr & 0x7; sr >>= 3; + xmc[43] = sr & 0x7; sr >>= 3; + xmc[44] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[45] = sr & 0x7; sr >>= 3; + xmc[46] = sr & 0x7; sr >>= 3; + xmc[47] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[48] = sr & 0x7; sr >>= 3; + xmc[49] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[50] = sr & 0x7; sr >>= 3; + xmc[51] = sr & 0x7; sr >>= 3; + + s->frame_chain = sr & 0xf; + } + else { + sr = s->frame_chain; + sr |= (uword)*c++ << 4; /* 1 */ + LARc[0] = sr & 0x3f; sr >>= 6; + LARc[1] = sr & 0x3f; sr >>= 6; + sr = *c++; + LARc[2] = sr & 0x1f; sr >>= 5; + sr |= (uword)*c++ << 3; + LARc[3] = sr & 0x1f; sr >>= 5; + LARc[4] = sr & 0xf; sr >>= 4; + sr |= (uword)*c++ << 2; + LARc[5] = sr & 0xf; sr >>= 4; + LARc[6] = sr & 0x7; sr >>= 3; + LARc[7] = sr & 0x7; sr >>= 3; + sr = *c++; /* 5 */ + Nc[0] = sr & 0x7f; sr >>= 7; + sr |= (uword)*c++ << 1; + bc[0] = sr & 0x3; sr >>= 2; + Mc[0] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 5; + xmaxc[0] = sr & 0x3f; sr >>= 6; +#undef xmc +#define xmc (target + 12) + xmc[0] = sr & 0x7; sr >>= 3; + xmc[1] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[2] = sr & 0x7; sr >>= 3; + xmc[3] = sr & 0x7; sr >>= 3; + xmc[4] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[5] = sr & 0x7; sr >>= 3; + xmc[6] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; /* 10 */ + xmc[7] = sr & 0x7; sr >>= 3; + xmc[8] = sr & 0x7; sr >>= 3; + xmc[9] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[10] = sr & 0x7; sr >>= 3; + xmc[11] = sr & 0x7; sr >>= 3; + xmc[12] = sr & 0x7; sr >>= 3; + sr = *c++; + Nc[1] = sr & 0x7f; sr >>= 7; + sr |= (uword)*c++ << 1; + bc[1] = sr & 0x3; sr >>= 2; + Mc[1] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 5; + xmaxc[1] = sr & 0x3f; sr >>= 6; +#undef xmc +#define xmc (target + 29 - 13) + + xmc[13] = sr & 0x7; sr >>= 3; + xmc[14] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; /* 15 */ + xmc[15] = sr & 0x7; sr >>= 3; + xmc[16] = sr & 0x7; sr >>= 3; + xmc[17] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[18] = sr & 0x7; sr >>= 3; + xmc[19] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[20] = sr & 0x7; sr >>= 3; + xmc[21] = sr & 0x7; sr >>= 3; + xmc[22] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[23] = sr & 0x7; sr >>= 3; + xmc[24] = sr & 0x7; sr >>= 3; + xmc[25] = sr & 0x7; sr >>= 3; + sr = *c++; + Nc[2] = sr & 0x7f; sr >>= 7; + sr |= (uword)*c++ << 1; /* 20 */ + bc[2] = sr & 0x3; sr >>= 2; + Mc[2] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 5; + xmaxc[2] = sr & 0x3f; sr >>= 6; +#undef xmc +#define xmc (target + 46 - 26) + xmc[26] = sr & 0x7; sr >>= 3; + xmc[27] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[28] = sr & 0x7; sr >>= 3; + xmc[29] = sr & 0x7; sr >>= 3; + xmc[30] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[31] = sr & 0x7; sr >>= 3; + xmc[32] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[33] = sr & 0x7; sr >>= 3; + xmc[34] = sr & 0x7; sr >>= 3; + xmc[35] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; /* 25 */ + xmc[36] = sr & 0x7; sr >>= 3; + xmc[37] = sr & 0x7; sr >>= 3; + xmc[38] = sr & 0x7; sr >>= 3; + sr = *c++; + Nc[3] = sr & 0x7f; sr >>= 7; + sr |= (uword)*c++ << 1; + bc[3] = sr & 0x3; sr >>= 2; + Mc[3] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 5; + xmaxc[3] = sr & 0x3f; sr >>= 6; + +#undef xmc +#define xmc (target + 63 - 39) + + xmc[39] = sr & 0x7; sr >>= 3; + xmc[40] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[41] = sr & 0x7; sr >>= 3; + xmc[42] = sr & 0x7; sr >>= 3; + xmc[43] = sr & 0x7; sr >>= 3; + sr = *c++; /* 30 */ + xmc[44] = sr & 0x7; sr >>= 3; + xmc[45] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[46] = sr & 0x7; sr >>= 3; + xmc[47] = sr & 0x7; sr >>= 3; + xmc[48] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[49] = sr & 0x7; sr >>= 3; + xmc[50] = sr & 0x7; sr >>= 3; + xmc[51] = sr & 0x7; sr >>= 3; + } + } + else +#endif + { + /* GSM_MAGIC = (*c >> 4) & 0xF; */ + + if (((*c >> 4) & 0x0F) != GSM_MAGIC) return -1; + + LARc[0] = (*c++ & 0xF) << 2; /* 1 */ + LARc[0] |= (*c >> 6) & 0x3; + LARc[1] = *c++ & 0x3F; + LARc[2] = (*c >> 3) & 0x1F; + LARc[3] = (*c++ & 0x7) << 2; + LARc[3] |= (*c >> 6) & 0x3; + LARc[4] = (*c >> 2) & 0xF; + LARc[5] = (*c++ & 0x3) << 2; + LARc[5] |= (*c >> 6) & 0x3; + LARc[6] = (*c >> 3) & 0x7; + LARc[7] = *c++ & 0x7; + + Nc[0] = (*c >> 1) & 0x7F; + + bc[0] = (*c++ & 0x1) << 1; + bc[0] |= (*c >> 7) & 0x1; + + Mc[0] = (*c >> 5) & 0x3; + + xmaxc[0] = (*c++ & 0x1F) << 1; + xmaxc[0] |= (*c >> 7) & 0x1; + +#undef xmc +#define xmc (target + 12) + + xmc[0] = (*c >> 4) & 0x7; + xmc[1] = (*c >> 1) & 0x7; + xmc[2] = (*c++ & 0x1) << 2; + xmc[2] |= (*c >> 6) & 0x3; + xmc[3] = (*c >> 3) & 0x7; + xmc[4] = *c++ & 0x7; + xmc[5] = (*c >> 5) & 0x7; + xmc[6] = (*c >> 2) & 0x7; + xmc[7] = (*c++ & 0x3) << 1; /* 10 */ + xmc[7] |= (*c >> 7) & 0x1; + xmc[8] = (*c >> 4) & 0x7; + xmc[9] = (*c >> 1) & 0x7; + xmc[10] = (*c++ & 0x1) << 2; + xmc[10] |= (*c >> 6) & 0x3; + xmc[11] = (*c >> 3) & 0x7; + xmc[12] = *c++ & 0x7; + + Nc[1] = (*c >> 1) & 0x7F; + + bc[1] = (*c++ & 0x1) << 1; + bc[1] |= (*c >> 7) & 0x1; + + Mc[1] = (*c >> 5) & 0x3; + + xmaxc[1] = (*c++ & 0x1F) << 1; + xmaxc[1] |= (*c >> 7) & 0x1; + +#undef xmc +#define xmc (target + 29 - 13) + + xmc[13] = (*c >> 4) & 0x7; + xmc[14] = (*c >> 1) & 0x7; + xmc[15] = (*c++ & 0x1) << 2; + xmc[15] |= (*c >> 6) & 0x3; + xmc[16] = (*c >> 3) & 0x7; + xmc[17] = *c++ & 0x7; + xmc[18] = (*c >> 5) & 0x7; + xmc[19] = (*c >> 2) & 0x7; + xmc[20] = (*c++ & 0x3) << 1; + xmc[20] |= (*c >> 7) & 0x1; + xmc[21] = (*c >> 4) & 0x7; + xmc[22] = (*c >> 1) & 0x7; + xmc[23] = (*c++ & 0x1) << 2; + xmc[23] |= (*c >> 6) & 0x3; + xmc[24] = (*c >> 3) & 0x7; + xmc[25] = *c++ & 0x7; + + Nc[2] = (*c >> 1) & 0x7F; + + bc[2] = (*c++ & 0x1) << 1; /* 20 */ + bc[2] |= (*c >> 7) & 0x1; + + Mc[2] = (*c >> 5) & 0x3; + + xmaxc[2] = (*c++ & 0x1F) << 1; + xmaxc[2] |= (*c >> 7) & 0x1; + +#undef xmc +#define xmc (target + 46 - 26) + + xmc[26] = (*c >> 4) & 0x7; + xmc[27] = (*c >> 1) & 0x7; + xmc[28] = (*c++ & 0x1) << 2; + xmc[28] |= (*c >> 6) & 0x3; + xmc[29] = (*c >> 3) & 0x7; + xmc[30] = *c++ & 0x7; + xmc[31] = (*c >> 5) & 0x7; + xmc[32] = (*c >> 2) & 0x7; + xmc[33] = (*c++ & 0x3) << 1; + xmc[33] |= (*c >> 7) & 0x1; + xmc[34] = (*c >> 4) & 0x7; + xmc[35] = (*c >> 1) & 0x7; + xmc[36] = (*c++ & 0x1) << 2; + xmc[36] |= (*c >> 6) & 0x3; + xmc[37] = (*c >> 3) & 0x7; + xmc[38] = *c++ & 0x7; + + Nc[3] = (*c >> 1) & 0x7F; + + bc[3] = (*c++ & 0x1) << 1; + bc[3] |= (*c >> 7) & 0x1; + + Mc[3] = (*c >> 5) & 0x3; + + xmaxc[3] = (*c++ & 0x1F) << 1; + xmaxc[3] |= (*c >> 7) & 0x1; + +#undef xmc +#define xmc (target + 63 - 39) + + xmc[39] = (*c >> 4) & 0x7; + xmc[40] = (*c >> 1) & 0x7; + xmc[41] = (*c++ & 0x1) << 2; + xmc[41] |= (*c >> 6) & 0x3; + xmc[42] = (*c >> 3) & 0x7; + xmc[43] = *c++ & 0x7; /* 30 */ + xmc[44] = (*c >> 5) & 0x7; + xmc[45] = (*c >> 2) & 0x7; + xmc[46] = (*c++ & 0x3) << 1; + xmc[46] |= (*c >> 7) & 0x1; + xmc[47] = (*c >> 4) & 0x7; + xmc[48] = (*c >> 1) & 0x7; + xmc[49] = (*c++ & 0x1) << 2; + xmc[49] |= (*c >> 6) & 0x3; + xmc[50] = (*c >> 3) & 0x7; + xmc[51] = *c & 0x7; /* 33 */ + } + + return 0; +} diff --git a/src/audio/gsm/src/gsm_implode.cpp b/src/audio/gsm/src/gsm_implode.cpp new file mode 100644 index 0000000..39b79cb --- /dev/null +++ b/src/audio/gsm/src/gsm_implode.cpp @@ -0,0 +1,515 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/* $Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/gsm_implode.c,v 1.2 1996/07/02 14:32:43 jutta Exp jutta $ */ + +#include "private.h" + +#include "gsm.h" +#include "proto.h" + +void gsm_implode P3((s, source, c), gsm s, gsm_signal * source, gsm_byte * c) +{ + /* variable size index + + GSM_MAGIC 4 - + + LARc[0] 6 0 + LARc[1] 6 1 + LARc[2] 5 2 + LARc[3] 5 3 + LARc[4] 4 4 + LARc[5] 4 5 + LARc[6] 3 6 + LARc[7] 3 7 + + Nc[0] 7 8 + bc[0] 2 9 + Mc[0] 2 10 + xmaxc[0] 6 11 + xmc[0] 3 12 + xmc[1] 3 13 + xmc[2] 3 14 + xmc[3] 3 15 + xmc[4] 3 16 + xmc[5] 3 17 + xmc[6] 3 18 + xmc[7] 3 19 + xmc[8] 3 20 + xmc[9] 3 21 + xmc[10] 3 22 + xmc[11] 3 23 + xmc[12] 3 24 + + Nc[1] 7 25 + bc[1] 2 26 + Mc[1] 2 27 + xmaxc[1] 6 28 + xmc[13] 3 29 + xmc[14] 3 30 + xmc[15] 3 31 + xmc[16] 3 32 + xmc[17] 3 33 + xmc[18] 3 34 + xmc[19] 3 35 + xmc[20] 3 36 + xmc[21] 3 37 + xmc[22] 3 38 + xmc[23] 3 39 + xmc[24] 3 40 + xmc[25] 3 41 + + Nc[2] 7 42 + bc[2] 2 43 + Mc[2] 2 44 + xmaxc[2] 6 45 + xmc[26] 3 46 + xmc[27] 3 47 + xmc[28] 3 48 + xmc[29] 3 49 + xmc[30] 3 50 + xmc[31] 3 51 + xmc[32] 3 52 + xmc[33] 3 53 + xmc[34] 3 54 + xmc[35] 3 55 + xmc[36] 3 56 + xmc[37] 3 57 + xmc[38] 3 58 + + Nc[3] 7 59 + bc[3] 2 60 + Mc[3] 2 61 + xmaxc[3] 6 62 + xmc[39] 3 63 + xmc[40] 3 64 + xmc[41] 3 65 + xmc[42] 3 66 + xmc[43] 3 67 + xmc[44] 3 68 + xmc[45] 3 69 + xmc[46] 3 70 + xmc[47] 3 71 + xmc[48] 3 72 + xmc[49] 3 73 + xmc[50] 3 74 + xmc[51] 3 75 + */ + + /* There are 76 parameters per frame. The first eight are + * unique. The remaining 68 are four identical subframes of + * 17 parameters each. gsm_implode converts from a representation + * of these parameters as values in one array of signed words + * to the "packed" version of a GSM frame. + */ + +# define LARc source +# define Nc *((gsm_signal (*) [17])(source + 8)) +# define bc *((gsm_signal (*) [17])(source + 9)) +# define Mc *((gsm_signal (*) [17])(source + 10)) +# define xmaxc *((gsm_signal (*) [17])(source + 11)) + +#ifdef WAV49 + if (s->wav_fmt) { + + uword sr = 0; + if (s->frame_index == 0) { + + sr = *c++; + LARc[0] = sr & 0x3f; sr >>= 6; + sr |= (uword)*c++ << 2; + LARc[1] = sr & 0x3f; sr >>= 6; + sr |= (uword)*c++ << 4; + LARc[2] = sr & 0x1f; sr >>= 5; + LARc[3] = sr & 0x1f; sr >>= 5; + sr |= (uword)*c++ << 2; + LARc[4] = sr & 0xf; sr >>= 4; + LARc[5] = sr & 0xf; sr >>= 4; + sr |= (uword)*c++ << 2; /* 5 */ + LARc[6] = sr & 0x7; sr >>= 3; + LARc[7] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 4; + Nc[0] = sr & 0x7f; sr >>= 7; + bc[0] = sr & 0x3; sr >>= 2; + Mc[0] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 1; + xmaxc[0] = sr & 0x3f; sr >>= 6; +#undef xmc +#define xmc (source + 12) + xmc[0] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[1] = sr & 0x7; sr >>= 3; + xmc[2] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[3] = sr & 0x7; sr >>= 3; + xmc[4] = sr & 0x7; sr >>= 3; + xmc[5] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; /* 10 */ + xmc[6] = sr & 0x7; sr >>= 3; + xmc[7] = sr & 0x7; sr >>= 3; + xmc[8] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[9] = sr & 0x7; sr >>= 3; + xmc[10] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[11] = sr & 0x7; sr >>= 3; + xmc[12] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 4; + Nc[1] = sr & 0x7f; sr >>= 7; + bc[1] = sr & 0x3; sr >>= 2; + Mc[1] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 1; + xmaxc[1] = sr & 0x3f; sr >>= 6; +#undef xmc +#define xmc (source + 29 - 13) + xmc[13] = sr & 0x7; sr >>= 3; + sr = *c++; /* 15 */ + xmc[14] = sr & 0x7; sr >>= 3; + xmc[15] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[16] = sr & 0x7; sr >>= 3; + xmc[17] = sr & 0x7; sr >>= 3; + xmc[18] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[19] = sr & 0x7; sr >>= 3; + xmc[20] = sr & 0x7; sr >>= 3; + xmc[21] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[22] = sr & 0x7; sr >>= 3; + xmc[23] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[24] = sr & 0x7; sr >>= 3; + xmc[25] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 4; /* 20 */ + Nc[2] = sr & 0x7f; sr >>= 7; + bc[2] = sr & 0x3; sr >>= 2; + Mc[2] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 1; + xmaxc[2] = sr & 0x3f; sr >>= 6; +#undef xmc +#define xmc (source + 46 - 26) + xmc[26] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[27] = sr & 0x7; sr >>= 3; + xmc[28] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[29] = sr & 0x7; sr >>= 3; + xmc[30] = sr & 0x7; sr >>= 3; + xmc[31] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[32] = sr & 0x7; sr >>= 3; + xmc[33] = sr & 0x7; sr >>= 3; + xmc[34] = sr & 0x7; sr >>= 3; + sr = *c++; /* 25 */ + xmc[35] = sr & 0x7; sr >>= 3; + xmc[36] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[37] = sr & 0x7; sr >>= 3; + xmc[38] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 4; + Nc[3] = sr & 0x7f; sr >>= 7; + bc[3] = sr & 0x3; sr >>= 2; + Mc[3] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 1; + xmaxc[3] = sr & 0x3f; sr >>= 6; +#undef xmc +#define xmc (source + 63 - 39) + + xmc[39] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[40] = sr & 0x7; sr >>= 3; + xmc[41] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; /* 30 */ + xmc[42] = sr & 0x7; sr >>= 3; + xmc[43] = sr & 0x7; sr >>= 3; + xmc[44] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[45] = sr & 0x7; sr >>= 3; + xmc[46] = sr & 0x7; sr >>= 3; + xmc[47] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[48] = sr & 0x7; sr >>= 3; + xmc[49] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[50] = sr & 0x7; sr >>= 3; + xmc[51] = sr & 0x7; sr >>= 3; + + s->frame_chain = sr & 0xf; + } + else { + sr = s->frame_chain; + sr |= (uword)*c++ << 4; /* 1 */ + LARc[0] = sr & 0x3f; sr >>= 6; + LARc[1] = sr & 0x3f; sr >>= 6; + sr = *c++; + LARc[2] = sr & 0x1f; sr >>= 5; + sr |= (uword)*c++ << 3; + LARc[3] = sr & 0x1f; sr >>= 5; + LARc[4] = sr & 0xf; sr >>= 4; + sr |= (uword)*c++ << 2; + LARc[5] = sr & 0xf; sr >>= 4; + LARc[6] = sr & 0x7; sr >>= 3; + LARc[7] = sr & 0x7; sr >>= 3; + sr = *c++; /* 5 */ + Nc[0] = sr & 0x7f; sr >>= 7; + sr |= (uword)*c++ << 1; + bc[0] = sr & 0x3; sr >>= 2; + Mc[0] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 5; + xmaxc[0] = sr & 0x3f; sr >>= 6; +#undef xmc +#define xmc (source + 12) + xmc[0] = sr & 0x7; sr >>= 3; + xmc[1] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[2] = sr & 0x7; sr >>= 3; + xmc[3] = sr & 0x7; sr >>= 3; + xmc[4] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[5] = sr & 0x7; sr >>= 3; + xmc[6] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; /* 10 */ + xmc[7] = sr & 0x7; sr >>= 3; + xmc[8] = sr & 0x7; sr >>= 3; + xmc[9] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[10] = sr & 0x7; sr >>= 3; + xmc[11] = sr & 0x7; sr >>= 3; + xmc[12] = sr & 0x7; sr >>= 3; + sr = *c++; + Nc[1] = sr & 0x7f; sr >>= 7; + sr |= (uword)*c++ << 1; + bc[1] = sr & 0x3; sr >>= 2; + Mc[1] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 5; + xmaxc[1] = sr & 0x3f; sr >>= 6; +#undef xmc +#define xmc (source + 29 - 13) + xmc[13] = sr & 0x7; sr >>= 3; + xmc[14] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; /* 15 */ + xmc[15] = sr & 0x7; sr >>= 3; + xmc[16] = sr & 0x7; sr >>= 3; + xmc[17] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[18] = sr & 0x7; sr >>= 3; + xmc[19] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[20] = sr & 0x7; sr >>= 3; + xmc[21] = sr & 0x7; sr >>= 3; + xmc[22] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[23] = sr & 0x7; sr >>= 3; + xmc[24] = sr & 0x7; sr >>= 3; + xmc[25] = sr & 0x7; sr >>= 3; + sr = *c++; + Nc[2] = sr & 0x7f; sr >>= 7; + sr |= (uword)*c++ << 1; /* 20 */ + bc[2] = sr & 0x3; sr >>= 2; + Mc[2] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 5; + xmaxc[2] = sr & 0x3f; sr >>= 6; +#undef xmc +#define xmc (source + 46 - 26) + xmc[26] = sr & 0x7; sr >>= 3; + xmc[27] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[28] = sr & 0x7; sr >>= 3; + xmc[29] = sr & 0x7; sr >>= 3; + xmc[30] = sr & 0x7; sr >>= 3; + sr = *c++; + xmc[31] = sr & 0x7; sr >>= 3; + xmc[32] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[33] = sr & 0x7; sr >>= 3; + xmc[34] = sr & 0x7; sr >>= 3; + xmc[35] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; /* 25 */ + xmc[36] = sr & 0x7; sr >>= 3; + xmc[37] = sr & 0x7; sr >>= 3; + xmc[38] = sr & 0x7; sr >>= 3; + sr = *c++; + Nc[3] = sr & 0x7f; sr >>= 7; + sr |= (uword)*c++ << 1; + bc[3] = sr & 0x3; sr >>= 2; + Mc[3] = sr & 0x3; sr >>= 2; + sr |= (uword)*c++ << 5; + xmaxc[3] = sr & 0x3f; sr >>= 6; +#undef xmc +#define xmc (source + 63 - 39) + + xmc[39] = sr & 0x7; sr >>= 3; + xmc[40] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[41] = sr & 0x7; sr >>= 3; + xmc[42] = sr & 0x7; sr >>= 3; + xmc[43] = sr & 0x7; sr >>= 3; + sr = *c++; /* 30 */ + xmc[44] = sr & 0x7; sr >>= 3; + xmc[45] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 2; + xmc[46] = sr & 0x7; sr >>= 3; + xmc[47] = sr & 0x7; sr >>= 3; + xmc[48] = sr & 0x7; sr >>= 3; + sr |= (uword)*c++ << 1; + xmc[49] = sr & 0x7; sr >>= 3; + xmc[50] = sr & 0x7; sr >>= 3; + xmc[51] = sr & 0x7; sr >>= 3; + } + } + else +#endif + { + + *c++ = ((GSM_MAGIC & 0xF) << 4) /* 1 */ + | ((LARc[0] >> 2) & 0xF); + *c++ = ((LARc[0] & 0x3) << 6) + | (LARc[1] & 0x3F); + *c++ = ((LARc[2] & 0x1F) << 3) + | ((LARc[3] >> 2) & 0x7); + *c++ = ((LARc[3] & 0x3) << 6) + | ((LARc[4] & 0xF) << 2) + | ((LARc[5] >> 2) & 0x3); + *c++ = ((LARc[5] & 0x3) << 6) + | ((LARc[6] & 0x7) << 3) + | (LARc[7] & 0x7); + + + *c++ = ((Nc[0] & 0x7F) << 1) + + + | ((bc[0] >> 1) & 0x1); + *c++ = ((bc[0] & 0x1) << 7) + + + | ((Mc[0] & 0x3) << 5) + + | ((xmaxc[0] >> 1) & 0x1F); + *c++ = ((xmaxc[0] & 0x1) << 7) + +#undef xmc +#define xmc (source + 12) + + | ((xmc[0] & 0x7) << 4) + | ((xmc[1] & 0x7) << 1) + | ((xmc[2] >> 2) & 0x1); + *c++ = ((xmc[2] & 0x3) << 6) + | ((xmc[3] & 0x7) << 3) + | (xmc[4] & 0x7); + *c++ = ((xmc[5] & 0x7) << 5) /* 10 */ + | ((xmc[6] & 0x7) << 2) + | ((xmc[7] >> 1) & 0x3); + *c++ = ((xmc[7] & 0x1) << 7) + | ((xmc[8] & 0x7) << 4) + | ((xmc[9] & 0x7) << 1) + | ((xmc[10] >> 2) & 0x1); + *c++ = ((xmc[10] & 0x3) << 6) + | ((xmc[11] & 0x7) << 3) + | (xmc[12] & 0x7); + + + *c++ = ((Nc[1] & 0x7F) << 1) + + + | ((bc[1] >> 1) & 0x1); + *c++ = ((bc[1] & 0x1) << 7) + + + | ((Mc[1] & 0x3) << 5) + + + | ((xmaxc[1] >> 1) & 0x1F); + *c++ = ((xmaxc[1] & 0x1) << 7) + +#undef xmc +#define xmc (source + 29 - 13) + + | ((xmc[13] & 0x7) << 4) + | ((xmc[14] & 0x7) << 1) + | ((xmc[15] >> 2) & 0x1); + *c++ = ((xmc[15] & 0x3) << 6) + | ((xmc[16] & 0x7) << 3) + | (xmc[17] & 0x7); + *c++ = ((xmc[18] & 0x7) << 5) + | ((xmc[19] & 0x7) << 2) + | ((xmc[20] >> 1) & 0x3); + *c++ = ((xmc[20] & 0x1) << 7) + | ((xmc[21] & 0x7) << 4) + | ((xmc[22] & 0x7) << 1) + | ((xmc[23] >> 2) & 0x1); + *c++ = ((xmc[23] & 0x3) << 6) + | ((xmc[24] & 0x7) << 3) + | (xmc[25] & 0x7); + + + *c++ = ((Nc[2] & 0x7F) << 1) /* 20 */ + + + | ((bc[2] >> 1) & 0x1); + *c++ = ((bc[2] & 0x1) << 7) + + + | ((Mc[2] & 0x3) << 5) + + + | ((xmaxc[2] >> 1) & 0x1F); + *c++ = ((xmaxc[2] & 0x1) << 7) + +#undef xmc +#define xmc (source + 46 - 26) + + | ((xmc[26] & 0x7) << 4) + | ((xmc[27] & 0x7) << 1) + | ((xmc[28] >> 2) & 0x1); + *c++ = ((xmc[28] & 0x3) << 6) + | ((xmc[29] & 0x7) << 3) + | (xmc[30] & 0x7); + *c++ = ((xmc[31] & 0x7) << 5) + | ((xmc[32] & 0x7) << 2) + | ((xmc[33] >> 1) & 0x3); + *c++ = ((xmc[33] & 0x1) << 7) + | ((xmc[34] & 0x7) << 4) + | ((xmc[35] & 0x7) << 1) + | ((xmc[36] >> 2) & 0x1); + *c++ = ((xmc[36] & 0x3) << 6) + | ((xmc[37] & 0x7) << 3) + | (xmc[38] & 0x7); + + + *c++ = ((Nc[3] & 0x7F) << 1) + + + | ((bc[3] >> 1) & 0x1); + *c++ = ((bc[3] & 0x1) << 7) + + + | ((Mc[3] & 0x3) << 5) + + + | ((xmaxc[3] >> 1) & 0x1F); + *c++ = ((xmaxc[3] & 0x1) << 7) + +#undef xmc +#define xmc (source + 63 - 39) + + | ((xmc[39] & 0x7) << 4) + | ((xmc[40] & 0x7) << 1) + | ((xmc[41] >> 2) & 0x1); + *c++ = ((xmc[41] & 0x3) << 6) /* 30 */ + | ((xmc[42] & 0x7) << 3) + | (xmc[43] & 0x7); + *c++ = ((xmc[44] & 0x7) << 5) + | ((xmc[45] & 0x7) << 2) + | ((xmc[46] >> 1) & 0x3); + *c++ = ((xmc[46] & 0x1) << 7) + | ((xmc[47] & 0x7) << 4) + | ((xmc[48] & 0x7) << 1) + | ((xmc[49] >> 2) & 0x1); + *c++ = ((xmc[49] & 0x3) << 6) + | ((xmc[50] & 0x7) << 3) + | (xmc[51] & 0x7); + } +} diff --git a/src/audio/gsm/src/gsm_option.cpp b/src/audio/gsm/src/gsm_option.cpp new file mode 100644 index 0000000..274b7b1 --- /dev/null +++ b/src/audio/gsm/src/gsm_option.cpp @@ -0,0 +1,69 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/* $Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/gsm_option.c,v 1.3 1996/07/02 09:59:05 jutta Exp $ */ + +#include "private.h" + +#include "gsm.h" +#include "proto.h" + +int gsm_option P3((r, opt, val), gsm r, int opt, int * val) +{ + int result = -1; + + switch (opt) { + case GSM_OPT_LTP_CUT: +#ifdef LTP_CUT + result = r->ltp_cut; + if (val) r->ltp_cut = *val; +#endif + break; + + case GSM_OPT_VERBOSE: +#ifndef NDEBUG + result = r->verbose; + if (val) r->verbose = *val; +#endif + break; + + case GSM_OPT_FAST: + +#if defined(FAST) && defined(USE_FLOAT_MUL) + result = r->fast; + if (val) r->fast = !!*val; +#endif + break; + + case GSM_OPT_FRAME_CHAIN: + +#ifdef WAV49 + result = r->frame_chain; + if (val) r->frame_chain = *val; +#endif + break; + + case GSM_OPT_FRAME_INDEX: + +#ifdef WAV49 + result = r->frame_index; + if (val) r->frame_index = *val; +#endif + break; + + case GSM_OPT_WAV49: + +#ifdef WAV49 + result = r->wav_fmt; + if (val) r->wav_fmt = !!*val; +#endif + break; + + default: + break; + } + return result; +} diff --git a/src/audio/gsm/src/gsm_print.cpp b/src/audio/gsm/src/gsm_print.cpp new file mode 100644 index 0000000..47f753b --- /dev/null +++ b/src/audio/gsm/src/gsm_print.cpp @@ -0,0 +1,167 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/* $Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/gsm_print.c,v 1.1 1992/10/28 00:15:50 jutta Exp $ */ + +#include + +#include "private.h" + +#include "gsm.h" +#include "proto.h" + +int gsm_print P3((f, s, c), FILE * f, gsm s, gsm_byte * c) +{ + word LARc[8], Nc[4], Mc[4], bc[4], xmaxc[4], xmc[13*4]; + + /* GSM_MAGIC = (*c >> 4) & 0xF; */ + + if (((*c >> 4) & 0x0F) != GSM_MAGIC) return -1; + + LARc[0] = (*c++ & 0xF) << 2; /* 1 */ + LARc[0] |= (*c >> 6) & 0x3; + LARc[1] = *c++ & 0x3F; + LARc[2] = (*c >> 3) & 0x1F; + LARc[3] = (*c++ & 0x7) << 2; + LARc[3] |= (*c >> 6) & 0x3; + LARc[4] = (*c >> 2) & 0xF; + LARc[5] = (*c++ & 0x3) << 2; + LARc[5] |= (*c >> 6) & 0x3; + LARc[6] = (*c >> 3) & 0x7; + LARc[7] = *c++ & 0x7; + + + Nc[0] = (*c >> 1) & 0x7F; + bc[0] = (*c++ & 0x1) << 1; + bc[0] |= (*c >> 7) & 0x1; + Mc[0] = (*c >> 5) & 0x3; + xmaxc[0] = (*c++ & 0x1F) << 1; + xmaxc[0] |= (*c >> 7) & 0x1; + xmc[0] = (*c >> 4) & 0x7; + xmc[1] = (*c >> 1) & 0x7; + xmc[2] = (*c++ & 0x1) << 2; + xmc[2] |= (*c >> 6) & 0x3; + xmc[3] = (*c >> 3) & 0x7; + xmc[4] = *c++ & 0x7; + xmc[5] = (*c >> 5) & 0x7; + xmc[6] = (*c >> 2) & 0x7; + xmc[7] = (*c++ & 0x3) << 1; /* 10 */ + xmc[7] |= (*c >> 7) & 0x1; + xmc[8] = (*c >> 4) & 0x7; + xmc[9] = (*c >> 1) & 0x7; + xmc[10] = (*c++ & 0x1) << 2; + xmc[10] |= (*c >> 6) & 0x3; + xmc[11] = (*c >> 3) & 0x7; + xmc[12] = *c++ & 0x7; + + Nc[1] = (*c >> 1) & 0x7F; + bc[1] = (*c++ & 0x1) << 1; + bc[1] |= (*c >> 7) & 0x1; + Mc[1] = (*c >> 5) & 0x3; + xmaxc[1] = (*c++ & 0x1F) << 1; + xmaxc[1] |= (*c >> 7) & 0x1; + xmc[13] = (*c >> 4) & 0x7; + xmc[14] = (*c >> 1) & 0x7; + xmc[15] = (*c++ & 0x1) << 2; + xmc[15] |= (*c >> 6) & 0x3; + xmc[16] = (*c >> 3) & 0x7; + xmc[17] = *c++ & 0x7; + xmc[18] = (*c >> 5) & 0x7; + xmc[19] = (*c >> 2) & 0x7; + xmc[20] = (*c++ & 0x3) << 1; + xmc[20] |= (*c >> 7) & 0x1; + xmc[21] = (*c >> 4) & 0x7; + xmc[22] = (*c >> 1) & 0x7; + xmc[23] = (*c++ & 0x1) << 2; + xmc[23] |= (*c >> 6) & 0x3; + xmc[24] = (*c >> 3) & 0x7; + xmc[25] = *c++ & 0x7; + + + Nc[2] = (*c >> 1) & 0x7F; + bc[2] = (*c++ & 0x1) << 1; /* 20 */ + bc[2] |= (*c >> 7) & 0x1; + Mc[2] = (*c >> 5) & 0x3; + xmaxc[2] = (*c++ & 0x1F) << 1; + xmaxc[2] |= (*c >> 7) & 0x1; + xmc[26] = (*c >> 4) & 0x7; + xmc[27] = (*c >> 1) & 0x7; + xmc[28] = (*c++ & 0x1) << 2; + xmc[28] |= (*c >> 6) & 0x3; + xmc[29] = (*c >> 3) & 0x7; + xmc[30] = *c++ & 0x7; + xmc[31] = (*c >> 5) & 0x7; + xmc[32] = (*c >> 2) & 0x7; + xmc[33] = (*c++ & 0x3) << 1; + xmc[33] |= (*c >> 7) & 0x1; + xmc[34] = (*c >> 4) & 0x7; + xmc[35] = (*c >> 1) & 0x7; + xmc[36] = (*c++ & 0x1) << 2; + xmc[36] |= (*c >> 6) & 0x3; + xmc[37] = (*c >> 3) & 0x7; + xmc[38] = *c++ & 0x7; + + Nc[3] = (*c >> 1) & 0x7F; + bc[3] = (*c++ & 0x1) << 1; + bc[3] |= (*c >> 7) & 0x1; + Mc[3] = (*c >> 5) & 0x3; + xmaxc[3] = (*c++ & 0x1F) << 1; + xmaxc[3] |= (*c >> 7) & 0x1; + + xmc[39] = (*c >> 4) & 0x7; + xmc[40] = (*c >> 1) & 0x7; + xmc[41] = (*c++ & 0x1) << 2; + xmc[41] |= (*c >> 6) & 0x3; + xmc[42] = (*c >> 3) & 0x7; + xmc[43] = *c++ & 0x7; /* 30 */ + xmc[44] = (*c >> 5) & 0x7; + xmc[45] = (*c >> 2) & 0x7; + xmc[46] = (*c++ & 0x3) << 1; + xmc[46] |= (*c >> 7) & 0x1; + xmc[47] = (*c >> 4) & 0x7; + xmc[48] = (*c >> 1) & 0x7; + xmc[49] = (*c++ & 0x1) << 2; + xmc[49] |= (*c >> 6) & 0x3; + xmc[50] = (*c >> 3) & 0x7; + xmc[51] = *c & 0x7; /* 33 */ + + fprintf(f, + "LARc:\t%2.2d %2.2d %2.2d %2.2d %2.2d %2.2d %2.2d %2.2d\n", + LARc[0],LARc[1],LARc[2],LARc[3],LARc[4],LARc[5],LARc[6],LARc[7]); + + fprintf(f, "#1: Nc %4.4d bc %d Mc %d xmaxc %d\n", + Nc[0], bc[0], Mc[0], xmaxc[0]); + fprintf(f, +"\t%.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d\n", + xmc[0],xmc[1],xmc[2],xmc[3],xmc[4],xmc[5],xmc[6], + xmc[7],xmc[8],xmc[9],xmc[10],xmc[11],xmc[12] ); + + fprintf(f, "#2: Nc %4.4d bc %d Mc %d xmaxc %d\n", + Nc[1], bc[1], Mc[1], xmaxc[1]); + fprintf(f, +"\t%.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d\n", + xmc[13+0],xmc[13+1],xmc[13+2],xmc[13+3],xmc[13+4],xmc[13+5], + xmc[13+6], xmc[13+7],xmc[13+8],xmc[13+9],xmc[13+10],xmc[13+11], + xmc[13+12] ); + + fprintf(f, "#3: Nc %4.4d bc %d Mc %d xmaxc %d\n", + Nc[2], bc[2], Mc[2], xmaxc[2]); + fprintf(f, +"\t%.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d\n", + xmc[26+0],xmc[26+1],xmc[26+2],xmc[26+3],xmc[26+4],xmc[26+5], + xmc[26+6], xmc[26+7],xmc[26+8],xmc[26+9],xmc[26+10],xmc[26+11], + xmc[26+12] ); + + fprintf(f, "#4: Nc %4.4d bc %d Mc %d xmaxc %d\n", + Nc[3], bc[3], Mc[3], xmaxc[3]); + fprintf(f, +"\t%.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d %.2d\n", + xmc[39+0],xmc[39+1],xmc[39+2],xmc[39+3],xmc[39+4],xmc[39+5], + xmc[39+6], xmc[39+7],xmc[39+8],xmc[39+9],xmc[39+10],xmc[39+11], + xmc[39+12] ); + + return 0; +} diff --git a/src/audio/gsm/src/long_term.cpp b/src/audio/gsm/src/long_term.cpp new file mode 100644 index 0000000..b2d2d3f --- /dev/null +++ b/src/audio/gsm/src/long_term.cpp @@ -0,0 +1,949 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/* $Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/long_term.c,v 1.6 1996/07/02 12:33:19 jutta Exp $ */ + +#include +#include + +#include "private.h" + +#include "gsm.h" +#include "proto.h" + +/* + * 4.2.11 .. 4.2.12 LONG TERM PREDICTOR (LTP) SECTION + */ + + +/* + * This module computes the LTP gain (bc) and the LTP lag (Nc) + * for the long term analysis filter. This is done by calculating a + * maximum of the cross-correlation function between the current + * sub-segment short term residual signal d[0..39] (output of + * the short term analysis filter; for simplification the index + * of this array begins at 0 and ends at 39 for each sub-segment of the + * RPE-LTP analysis) and the previous reconstructed short term + * residual signal dp[ -120 .. -1 ]. A dynamic scaling must be + * performed to avoid overflow. + */ + + /* The next procedure exists in six versions. First two integer + * version (if USE_FLOAT_MUL is not defined); then four floating + * point versions, twice with proper scaling (USE_FLOAT_MUL defined), + * once without (USE_FLOAT_MUL and FAST defined, and fast run-time + * option used). Every pair has first a Cut version (see the -C + * option to toast or the LTP_CUT option to gsm_option()), then the + * uncut one. (For a detailed explanation of why this is altogether + * a bad idea, see Henry Spencer and Geoff Collyer, ``#ifdef Considered + * Harmful''.) + */ + +#ifndef USE_FLOAT_MUL + +#ifdef LTP_CUT + +static void Cut_Calculation_of_the_LTP_parameters P5((st, d,dp,bc_out,Nc_out), + + struct gsm_state * st, + + register word * d, /* [0..39] IN */ + register word * dp, /* [-120..-1] IN */ + word * bc_out, /* OUT */ + word * Nc_out /* OUT */ +) +{ + register int k, lambda; + word Nc, bc; + word wt[40]; + + longword L_result; + longword L_max, L_power; + word R, S, dmax, scal, best_k; + word ltp_cut; + + register word temp, wt_k; + + /* Search of the optimum scaling of d[0..39]. + */ + dmax = 0; + for (k = 0; k <= 39; k++) { + temp = d[k]; + temp = GSM_ABS( temp ); + if (temp > dmax) { + dmax = temp; + best_k = k; + } + } + temp = 0; + if (dmax == 0) scal = 0; + else { + assert(dmax > 0); + temp = gsm_norm( (longword)dmax << 16 ); + } + if (temp > 6) scal = 0; + else scal = 6 - temp; + assert(scal >= 0); + + /* Search for the maximum cross-correlation and coding of the LTP lag + */ + L_max = 0; + Nc = 40; /* index for the maximum cross-correlation */ + wt_k = SASR(d[best_k], scal); + + for (lambda = 40; lambda <= 120; lambda++) { + L_result = (longword)wt_k * dp[best_k - lambda]; + if (L_result > L_max) { + Nc = lambda; + L_max = L_result; + } + } + *Nc_out = Nc; + L_max <<= 1; + + /* Rescaling of L_max + */ + assert(scal <= 100 && scal >= -100); + L_max = L_max >> (6 - scal); /* sub(6, scal) */ + + assert( Nc <= 120 && Nc >= 40); + + /* Compute the power of the reconstructed short term residual + * signal dp[..] + */ + L_power = 0; + for (k = 0; k <= 39; k++) { + + register longword L_temp; + + L_temp = SASR( dp[k - Nc], 3 ); + L_power += L_temp * L_temp; + } + L_power <<= 1; /* from L_MULT */ + + /* Normalization of L_max and L_power + */ + + if (L_max <= 0) { + *bc_out = 0; + return; + } + if (L_max >= L_power) { + *bc_out = 3; + return; + } + + temp = gsm_norm( L_power ); + + R = SASR( L_max << temp, 16 ); + S = SASR( L_power << temp, 16 ); + + /* Coding of the LTP gain + */ + + /* Table 4.3a must be used to obtain the level DLB[i] for the + * quantization of the LTP gain b to get the coded version bc. + */ + for (bc = 0; bc <= 2; bc++) if (R <= gsm_mult(S, gsm_DLB[bc])) break; + *bc_out = bc; +} + +#endif /* LTP_CUT */ + +static void Calculation_of_the_LTP_parameters P4((d,dp,bc_out,Nc_out), + register word * d, /* [0..39] IN */ + register word * dp, /* [-120..-1] IN */ + word * bc_out, /* OUT */ + word * Nc_out /* OUT */ +) +{ + register int k, lambda; + word Nc, bc; + word wt[40]; + + longword L_max, L_power; + word R, S, dmax, scal; + register word temp; + + /* Search of the optimum scaling of d[0..39]. + */ + dmax = 0; + + for (k = 0; k <= 39; k++) { + temp = d[k]; + temp = GSM_ABS( temp ); + if (temp > dmax) dmax = temp; + } + + temp = 0; + if (dmax == 0) scal = 0; + else { + assert(dmax > 0); + temp = gsm_norm( (longword)dmax << 16 ); + } + + if (temp > 6) scal = 0; + else scal = 6 - temp; + + assert(scal >= 0); + + /* Initialization of a working array wt + */ + + for (k = 0; k <= 39; k++) wt[k] = SASR( d[k], scal ); + + /* Search for the maximum cross-correlation and coding of the LTP lag + */ + L_max = 0; + Nc = 40; /* index for the maximum cross-correlation */ + + for (lambda = 40; lambda <= 120; lambda++) { + +# undef STEP +# define STEP(k) (longword)wt[k] * dp[k - lambda] + + register longword L_result; + + L_result = STEP(0) ; L_result += STEP(1) ; + L_result += STEP(2) ; L_result += STEP(3) ; + L_result += STEP(4) ; L_result += STEP(5) ; + L_result += STEP(6) ; L_result += STEP(7) ; + L_result += STEP(8) ; L_result += STEP(9) ; + L_result += STEP(10) ; L_result += STEP(11) ; + L_result += STEP(12) ; L_result += STEP(13) ; + L_result += STEP(14) ; L_result += STEP(15) ; + L_result += STEP(16) ; L_result += STEP(17) ; + L_result += STEP(18) ; L_result += STEP(19) ; + L_result += STEP(20) ; L_result += STEP(21) ; + L_result += STEP(22) ; L_result += STEP(23) ; + L_result += STEP(24) ; L_result += STEP(25) ; + L_result += STEP(26) ; L_result += STEP(27) ; + L_result += STEP(28) ; L_result += STEP(29) ; + L_result += STEP(30) ; L_result += STEP(31) ; + L_result += STEP(32) ; L_result += STEP(33) ; + L_result += STEP(34) ; L_result += STEP(35) ; + L_result += STEP(36) ; L_result += STEP(37) ; + L_result += STEP(38) ; L_result += STEP(39) ; + + if (L_result > L_max) { + + Nc = lambda; + L_max = L_result; + } + } + + *Nc_out = Nc; + + L_max <<= 1; + + /* Rescaling of L_max + */ + assert(scal <= 100 && scal >= -100); + L_max = L_max >> (6 - scal); /* sub(6, scal) */ + + assert( Nc <= 120 && Nc >= 40); + + /* Compute the power of the reconstructed short term residual + * signal dp[..] + */ + L_power = 0; + for (k = 0; k <= 39; k++) { + + register longword L_temp; + + L_temp = SASR( dp[k - Nc], 3 ); + L_power += L_temp * L_temp; + } + L_power <<= 1; /* from L_MULT */ + + /* Normalization of L_max and L_power + */ + + if (L_max <= 0) { + *bc_out = 0; + return; + } + if (L_max >= L_power) { + *bc_out = 3; + return; + } + + temp = gsm_norm( L_power ); + + R = SASR( L_max << temp, 16 ); + S = SASR( L_power << temp, 16 ); + + /* Coding of the LTP gain + */ + + /* Table 4.3a must be used to obtain the level DLB[i] for the + * quantization of the LTP gain b to get the coded version bc. + */ + for (bc = 0; bc <= 2; bc++) if (R <= gsm_mult(S, gsm_DLB[bc])) break; + *bc_out = bc; +} + +#else /* USE_FLOAT_MUL */ + +#ifdef LTP_CUT + +static void Cut_Calculation_of_the_LTP_parameters P5((st, d,dp,bc_out,Nc_out), + struct gsm_state * st, /* IN */ + register word * d, /* [0..39] IN */ + register word * dp, /* [-120..-1] IN */ + word * bc_out, /* OUT */ + word * Nc_out /* OUT */ +) +{ + register int k, lambda; + word Nc, bc; + word ltp_cut; + + float wt_float[40]; + float dp_float_base[120], * dp_float = dp_float_base + 120; + + longword L_max, L_power; + word R, S, dmax, scal; + register word temp; + + /* Search of the optimum scaling of d[0..39]. + */ + dmax = 0; + + for (k = 0; k <= 39; k++) { + temp = d[k]; + temp = GSM_ABS( temp ); + if (temp > dmax) dmax = temp; + } + + temp = 0; + if (dmax == 0) scal = 0; + else { + assert(dmax > 0); + temp = gsm_norm( (longword)dmax << 16 ); + } + + if (temp > 6) scal = 0; + else scal = 6 - temp; + + assert(scal >= 0); + ltp_cut = (longword)SASR(dmax, scal) * st->ltp_cut / 100; + + + /* Initialization of a working array wt + */ + + for (k = 0; k < 40; k++) { + register word w = SASR( d[k], scal ); + if (w < 0 ? w > -ltp_cut : w < ltp_cut) { + wt_float[k] = 0.0; + } + else { + wt_float[k] = w; + } + } + for (k = -120; k < 0; k++) dp_float[k] = dp[k]; + + /* Search for the maximum cross-correlation and coding of the LTP lag + */ + L_max = 0; + Nc = 40; /* index for the maximum cross-correlation */ + + for (lambda = 40; lambda <= 120; lambda += 9) { + + /* Calculate L_result for l = lambda .. lambda + 9. + */ + register float *lp = dp_float - lambda; + + register float W; + register float a = lp[-8], b = lp[-7], c = lp[-6], + d = lp[-5], e = lp[-4], f = lp[-3], + g = lp[-2], h = lp[-1]; + register float E; + register float S0 = 0, S1 = 0, S2 = 0, S3 = 0, S4 = 0, + S5 = 0, S6 = 0, S7 = 0, S8 = 0; + +# undef STEP +# define STEP(K, a, b, c, d, e, f, g, h) \ + if ((W = wt_float[K]) != 0.0) { \ + E = W * a; S8 += E; \ + E = W * b; S7 += E; \ + E = W * c; S6 += E; \ + E = W * d; S5 += E; \ + E = W * e; S4 += E; \ + E = W * f; S3 += E; \ + E = W * g; S2 += E; \ + E = W * h; S1 += E; \ + a = lp[K]; \ + E = W * a; S0 += E; } else (a = lp[K]) + +# define STEP_A(K) STEP(K, a, b, c, d, e, f, g, h) +# define STEP_B(K) STEP(K, b, c, d, e, f, g, h, a) +# define STEP_C(K) STEP(K, c, d, e, f, g, h, a, b) +# define STEP_D(K) STEP(K, d, e, f, g, h, a, b, c) +# define STEP_E(K) STEP(K, e, f, g, h, a, b, c, d) +# define STEP_F(K) STEP(K, f, g, h, a, b, c, d, e) +# define STEP_G(K) STEP(K, g, h, a, b, c, d, e, f) +# define STEP_H(K) STEP(K, h, a, b, c, d, e, f, g) + + STEP_A( 0); STEP_B( 1); STEP_C( 2); STEP_D( 3); + STEP_E( 4); STEP_F( 5); STEP_G( 6); STEP_H( 7); + + STEP_A( 8); STEP_B( 9); STEP_C(10); STEP_D(11); + STEP_E(12); STEP_F(13); STEP_G(14); STEP_H(15); + + STEP_A(16); STEP_B(17); STEP_C(18); STEP_D(19); + STEP_E(20); STEP_F(21); STEP_G(22); STEP_H(23); + + STEP_A(24); STEP_B(25); STEP_C(26); STEP_D(27); + STEP_E(28); STEP_F(29); STEP_G(30); STEP_H(31); + + STEP_A(32); STEP_B(33); STEP_C(34); STEP_D(35); + STEP_E(36); STEP_F(37); STEP_G(38); STEP_H(39); + + if (S0 > L_max) { L_max = S0; Nc = lambda; } + if (S1 > L_max) { L_max = S1; Nc = lambda + 1; } + if (S2 > L_max) { L_max = S2; Nc = lambda + 2; } + if (S3 > L_max) { L_max = S3; Nc = lambda + 3; } + if (S4 > L_max) { L_max = S4; Nc = lambda + 4; } + if (S5 > L_max) { L_max = S5; Nc = lambda + 5; } + if (S6 > L_max) { L_max = S6; Nc = lambda + 6; } + if (S7 > L_max) { L_max = S7; Nc = lambda + 7; } + if (S8 > L_max) { L_max = S8; Nc = lambda + 8; } + + } + *Nc_out = Nc; + + L_max <<= 1; + + /* Rescaling of L_max + */ + assert(scal <= 100 && scal >= -100); + L_max = L_max >> (6 - scal); /* sub(6, scal) */ + + assert( Nc <= 120 && Nc >= 40); + + /* Compute the power of the reconstructed short term residual + * signal dp[..] + */ + L_power = 0; + for (k = 0; k <= 39; k++) { + + register longword L_temp; + + L_temp = SASR( dp[k - Nc], 3 ); + L_power += L_temp * L_temp; + } + L_power <<= 1; /* from L_MULT */ + + /* Normalization of L_max and L_power + */ + + if (L_max <= 0) { + *bc_out = 0; + return; + } + if (L_max >= L_power) { + *bc_out = 3; + return; + } + + temp = gsm_norm( L_power ); + + R = SASR( L_max << temp, 16 ); + S = SASR( L_power << temp, 16 ); + + /* Coding of the LTP gain + */ + + /* Table 4.3a must be used to obtain the level DLB[i] for the + * quantization of the LTP gain b to get the coded version bc. + */ + for (bc = 0; bc <= 2; bc++) if (R <= gsm_mult(S, gsm_DLB[bc])) break; + *bc_out = bc; +} + +#endif /* LTP_CUT */ + +static void Calculation_of_the_LTP_parameters P4((d,dp,bc_out,Nc_out), + register word * d, /* [0..39] IN */ + register word * dp, /* [-120..-1] IN */ + word * bc_out, /* OUT */ + word * Nc_out /* OUT */ +) +{ + register int k, lambda; + word Nc, bc; + + float wt_float[40]; + float dp_float_base[120], * dp_float = dp_float_base + 120; + + longword L_max, L_power; + word R, S, dmax, scal; + register word temp; + + /* Search of the optimum scaling of d[0..39]. + */ + dmax = 0; + + for (k = 0; k <= 39; k++) { + temp = d[k]; + temp = GSM_ABS( temp ); + if (temp > dmax) dmax = temp; + } + + temp = 0; + if (dmax == 0) scal = 0; + else { + assert(dmax > 0); + temp = gsm_norm( (longword)dmax << 16 ); + } + + if (temp > 6) scal = 0; + else scal = 6 - temp; + + assert(scal >= 0); + + /* Initialization of a working array wt + */ + + for (k = 0; k < 40; k++) wt_float[k] = SASR( d[k], scal ); + for (k = -120; k < 0; k++) dp_float[k] = dp[k]; + + /* Search for the maximum cross-correlation and coding of the LTP lag + */ + L_max = 0; + Nc = 40; /* index for the maximum cross-correlation */ + + for (lambda = 40; lambda <= 120; lambda += 9) { + + /* Calculate L_result for l = lambda .. lambda + 9. + */ + register float *lp = dp_float - lambda; + + register float W; + register float a = lp[-8], b = lp[-7], c = lp[-6], + d = lp[-5], e = lp[-4], f = lp[-3], + g = lp[-2], h = lp[-1]; + register float E; + register float S0 = 0, S1 = 0, S2 = 0, S3 = 0, S4 = 0, + S5 = 0, S6 = 0, S7 = 0, S8 = 0; + +# undef STEP +# define STEP(K, a, b, c, d, e, f, g, h) \ + W = wt_float[K]; \ + E = W * a; S8 += E; \ + E = W * b; S7 += E; \ + E = W * c; S6 += E; \ + E = W * d; S5 += E; \ + E = W * e; S4 += E; \ + E = W * f; S3 += E; \ + E = W * g; S2 += E; \ + E = W * h; S1 += E; \ + a = lp[K]; \ + E = W * a; S0 += E + +# define STEP_A(K) STEP(K, a, b, c, d, e, f, g, h) +# define STEP_B(K) STEP(K, b, c, d, e, f, g, h, a) +# define STEP_C(K) STEP(K, c, d, e, f, g, h, a, b) +# define STEP_D(K) STEP(K, d, e, f, g, h, a, b, c) +# define STEP_E(K) STEP(K, e, f, g, h, a, b, c, d) +# define STEP_F(K) STEP(K, f, g, h, a, b, c, d, e) +# define STEP_G(K) STEP(K, g, h, a, b, c, d, e, f) +# define STEP_H(K) STEP(K, h, a, b, c, d, e, f, g) + + STEP_A( 0); STEP_B( 1); STEP_C( 2); STEP_D( 3); + STEP_E( 4); STEP_F( 5); STEP_G( 6); STEP_H( 7); + + STEP_A( 8); STEP_B( 9); STEP_C(10); STEP_D(11); + STEP_E(12); STEP_F(13); STEP_G(14); STEP_H(15); + + STEP_A(16); STEP_B(17); STEP_C(18); STEP_D(19); + STEP_E(20); STEP_F(21); STEP_G(22); STEP_H(23); + + STEP_A(24); STEP_B(25); STEP_C(26); STEP_D(27); + STEP_E(28); STEP_F(29); STEP_G(30); STEP_H(31); + + STEP_A(32); STEP_B(33); STEP_C(34); STEP_D(35); + STEP_E(36); STEP_F(37); STEP_G(38); STEP_H(39); + + if (S0 > L_max) { L_max = S0; Nc = lambda; } + if (S1 > L_max) { L_max = S1; Nc = lambda + 1; } + if (S2 > L_max) { L_max = S2; Nc = lambda + 2; } + if (S3 > L_max) { L_max = S3; Nc = lambda + 3; } + if (S4 > L_max) { L_max = S4; Nc = lambda + 4; } + if (S5 > L_max) { L_max = S5; Nc = lambda + 5; } + if (S6 > L_max) { L_max = S6; Nc = lambda + 6; } + if (S7 > L_max) { L_max = S7; Nc = lambda + 7; } + if (S8 > L_max) { L_max = S8; Nc = lambda + 8; } + } + *Nc_out = Nc; + + L_max <<= 1; + + /* Rescaling of L_max + */ + assert(scal <= 100 && scal >= -100); + L_max = L_max >> (6 - scal); /* sub(6, scal) */ + + assert( Nc <= 120 && Nc >= 40); + + /* Compute the power of the reconstructed short term residual + * signal dp[..] + */ + L_power = 0; + for (k = 0; k <= 39; k++) { + + register longword L_temp; + + L_temp = SASR( dp[k - Nc], 3 ); + L_power += L_temp * L_temp; + } + L_power <<= 1; /* from L_MULT */ + + /* Normalization of L_max and L_power + */ + + if (L_max <= 0) { + *bc_out = 0; + return; + } + if (L_max >= L_power) { + *bc_out = 3; + return; + } + + temp = gsm_norm( L_power ); + + R = SASR( L_max << temp, 16 ); + S = SASR( L_power << temp, 16 ); + + /* Coding of the LTP gain + */ + + /* Table 4.3a must be used to obtain the level DLB[i] for the + * quantization of the LTP gain b to get the coded version bc. + */ + for (bc = 0; bc <= 2; bc++) if (R <= gsm_mult(S, gsm_DLB[bc])) break; + *bc_out = bc; +} + +#ifdef FAST +#ifdef LTP_CUT + +static void Cut_Fast_Calculation_of_the_LTP_parameters P5((st, + d,dp,bc_out,Nc_out), + struct gsm_state * st, /* IN */ + register word * d, /* [0..39] IN */ + register word * dp, /* [-120..-1] IN */ + word * bc_out, /* OUT */ + word * Nc_out /* OUT */ +) +{ + register int k, lambda; + register float wt_float; + word Nc, bc; + word wt_max, best_k, ltp_cut; + + float dp_float_base[120], * dp_float = dp_float_base + 120; + + register float L_result, L_max, L_power; + + wt_max = 0; + + for (k = 0; k < 40; ++k) { + if ( d[k] > wt_max) wt_max = d[best_k = k]; + else if (-d[k] > wt_max) wt_max = -d[best_k = k]; + } + + assert(wt_max >= 0); + wt_float = (float)wt_max; + + for (k = -120; k < 0; ++k) dp_float[k] = (float)dp[k]; + + /* Search for the maximum cross-correlation and coding of the LTP lag + */ + L_max = 0; + Nc = 40; /* index for the maximum cross-correlation */ + + for (lambda = 40; lambda <= 120; lambda++) { + L_result = wt_float * dp_float[best_k - lambda]; + if (L_result > L_max) { + Nc = lambda; + L_max = L_result; + } + } + + *Nc_out = Nc; + if (L_max <= 0.) { + *bc_out = 0; + return; + } + + /* Compute the power of the reconstructed short term residual + * signal dp[..] + */ + dp_float -= Nc; + L_power = 0; + for (k = 0; k < 40; ++k) { + register float f = dp_float[k]; + L_power += f * f; + } + + if (L_max >= L_power) { + *bc_out = 3; + return; + } + + /* Coding of the LTP gain + * Table 4.3a must be used to obtain the level DLB[i] for the + * quantization of the LTP gain b to get the coded version bc. + */ + lambda = L_max / L_power * 32768.; + for (bc = 0; bc <= 2; ++bc) if (lambda <= gsm_DLB[bc]) break; + *bc_out = bc; +} + +#endif /* LTP_CUT */ + +static void Fast_Calculation_of_the_LTP_parameters P4((d,dp,bc_out,Nc_out), + register word * d, /* [0..39] IN */ + register word * dp, /* [-120..-1] IN */ + word * bc_out, /* OUT */ + word * Nc_out /* OUT */ +) +{ + register int k, lambda; + word Nc, bc; + + float wt_float[40]; + float dp_float_base[120], * dp_float = dp_float_base + 120; + + register float L_max, L_power; + + for (k = 0; k < 40; ++k) wt_float[k] = (float)d[k]; + for (k = -120; k < 0; ++k) dp_float[k] = (float)dp[k]; + + /* Search for the maximum cross-correlation and coding of the LTP lag + */ + L_max = 0; + Nc = 40; /* index for the maximum cross-correlation */ + + for (lambda = 40; lambda <= 120; lambda += 9) { + + /* Calculate L_result for l = lambda .. lambda + 9. + */ + register float *lp = dp_float - lambda; + + register float W; + register float a = lp[-8], b = lp[-7], c = lp[-6], + d = lp[-5], e = lp[-4], f = lp[-3], + g = lp[-2], h = lp[-1]; + register float E; + register float S0 = 0, S1 = 0, S2 = 0, S3 = 0, S4 = 0, + S5 = 0, S6 = 0, S7 = 0, S8 = 0; + +# undef STEP +# define STEP(K, a, b, c, d, e, f, g, h) \ + W = wt_float[K]; \ + E = W * a; S8 += E; \ + E = W * b; S7 += E; \ + E = W * c; S6 += E; \ + E = W * d; S5 += E; \ + E = W * e; S4 += E; \ + E = W * f; S3 += E; \ + E = W * g; S2 += E; \ + E = W * h; S1 += E; \ + a = lp[K]; \ + E = W * a; S0 += E + +# define STEP_A(K) STEP(K, a, b, c, d, e, f, g, h) +# define STEP_B(K) STEP(K, b, c, d, e, f, g, h, a) +# define STEP_C(K) STEP(K, c, d, e, f, g, h, a, b) +# define STEP_D(K) STEP(K, d, e, f, g, h, a, b, c) +# define STEP_E(K) STEP(K, e, f, g, h, a, b, c, d) +# define STEP_F(K) STEP(K, f, g, h, a, b, c, d, e) +# define STEP_G(K) STEP(K, g, h, a, b, c, d, e, f) +# define STEP_H(K) STEP(K, h, a, b, c, d, e, f, g) + + STEP_A( 0); STEP_B( 1); STEP_C( 2); STEP_D( 3); + STEP_E( 4); STEP_F( 5); STEP_G( 6); STEP_H( 7); + + STEP_A( 8); STEP_B( 9); STEP_C(10); STEP_D(11); + STEP_E(12); STEP_F(13); STEP_G(14); STEP_H(15); + + STEP_A(16); STEP_B(17); STEP_C(18); STEP_D(19); + STEP_E(20); STEP_F(21); STEP_G(22); STEP_H(23); + + STEP_A(24); STEP_B(25); STEP_C(26); STEP_D(27); + STEP_E(28); STEP_F(29); STEP_G(30); STEP_H(31); + + STEP_A(32); STEP_B(33); STEP_C(34); STEP_D(35); + STEP_E(36); STEP_F(37); STEP_G(38); STEP_H(39); + + if (S0 > L_max) { L_max = S0; Nc = lambda; } + if (S1 > L_max) { L_max = S1; Nc = lambda + 1; } + if (S2 > L_max) { L_max = S2; Nc = lambda + 2; } + if (S3 > L_max) { L_max = S3; Nc = lambda + 3; } + if (S4 > L_max) { L_max = S4; Nc = lambda + 4; } + if (S5 > L_max) { L_max = S5; Nc = lambda + 5; } + if (S6 > L_max) { L_max = S6; Nc = lambda + 6; } + if (S7 > L_max) { L_max = S7; Nc = lambda + 7; } + if (S8 > L_max) { L_max = S8; Nc = lambda + 8; } + } + *Nc_out = Nc; + + if (L_max <= 0.) { + *bc_out = 0; + return; + } + + /* Compute the power of the reconstructed short term residual + * signal dp[..] + */ + dp_float -= Nc; + L_power = 0; + for (k = 0; k < 40; ++k) { + register float f = dp_float[k]; + L_power += f * f; + } + + if (L_max >= L_power) { + *bc_out = 3; + return; + } + + /* Coding of the LTP gain + * Table 4.3a must be used to obtain the level DLB[i] for the + * quantization of the LTP gain b to get the coded version bc. + */ + lambda = L_max / L_power * 32768.; + for (bc = 0; bc <= 2; ++bc) if (lambda <= gsm_DLB[bc]) break; + *bc_out = bc; +} + +#endif /* FAST */ +#endif /* USE_FLOAT_MUL */ + + +/* 4.2.12 */ + +static void Long_term_analysis_filtering P6((bc,Nc,dp,d,dpp,e), + word bc, /* IN */ + word Nc, /* IN */ + register word * dp, /* previous d [-120..-1] IN */ + register word * d, /* d [0..39] IN */ + register word * dpp, /* estimate [0..39] OUT */ + register word * e /* long term res. signal [0..39] OUT */ +) +/* + * In this part, we have to decode the bc parameter to compute + * the samples of the estimate dpp[0..39]. The decoding of bc needs the + * use of table 4.3b. The long term residual signal e[0..39] + * is then calculated to be fed to the RPE encoding section. + */ +{ + register int k; + register longword ltmp; + +# undef STEP +# define STEP(BP) \ + for (k = 0; k <= 39; k++) { \ + dpp[k] = GSM_MULT_R( BP, dp[k - Nc]); \ + e[k] = GSM_SUB( d[k], dpp[k] ); \ + } + + switch (bc) { + case 0: STEP( 3277 ); break; + case 1: STEP( 11469 ); break; + case 2: STEP( 21299 ); break; + case 3: STEP( 32767 ); break; + } +} + +void Gsm_Long_Term_Predictor P7((S,d,dp,e,dpp,Nc,bc), /* 4x for 160 samples */ + + struct gsm_state * S, + + word * d, /* [0..39] residual signal IN */ + word * dp, /* [-120..-1] d' IN */ + + word * e, /* [0..39] OUT */ + word * dpp, /* [0..39] OUT */ + word * Nc, /* correlation lag OUT */ + word * bc /* gain factor OUT */ +) +{ + assert( d ); assert( dp ); assert( e ); + assert( dpp); assert( Nc ); assert( bc ); + +#if defined(FAST) && defined(USE_FLOAT_MUL) + if (S->fast) +#if defined (LTP_CUT) + if (S->ltp_cut) + Cut_Fast_Calculation_of_the_LTP_parameters(S, + d, dp, bc, Nc); + else +#endif /* LTP_CUT */ + Fast_Calculation_of_the_LTP_parameters(d, dp, bc, Nc ); + else +#endif /* FAST & USE_FLOAT_MUL */ +#ifdef LTP_CUT + if (S->ltp_cut) + Cut_Calculation_of_the_LTP_parameters(S, d, dp, bc, Nc); + else +#endif + Calculation_of_the_LTP_parameters(d, dp, bc, Nc); + + Long_term_analysis_filtering( *bc, *Nc, dp, d, dpp, e ); +} + +/* 4.3.2 */ +void Gsm_Long_Term_Synthesis_Filtering P5((S,Ncr,bcr,erp,drp), + struct gsm_state * S, + + word Ncr, + word bcr, + register word * erp, /* [0..39] IN */ + register word * drp /* [-120..-1] IN, [-120..40] OUT */ +) +/* + * This procedure uses the bcr and Ncr parameter to realize the + * long term synthesis filtering. The decoding of bcr needs + * table 4.3b. + */ +{ + register longword ltmp; /* for ADD */ + register int k; + word brp, drpp, Nr; + + /* Check the limits of Nr. + */ + Nr = Ncr < 40 || Ncr > 120 ? S->nrp : Ncr; + S->nrp = Nr; + assert(Nr >= 40 && Nr <= 120); + + /* Decoding of the LTP gain bcr + */ + brp = gsm_QLB[ bcr ]; + + /* Computation of the reconstructed short term residual + * signal drp[0..39] + */ + assert(brp != MIN_WORD); + + for (k = 0; k <= 39; k++) { + drpp = GSM_MULT_R( brp, drp[ k - Nr ] ); + drp[k] = GSM_ADD( erp[k], drpp ); + } + + /* + * Update of the reconstructed short term residual signal + * drp[ -1..-120 ] + */ + + for (k = 0; k <= 119; k++) drp[ -120 + k ] = drp[ -80 + k ]; +} diff --git a/src/audio/gsm/src/lpc.cpp b/src/audio/gsm/src/lpc.cpp new file mode 100644 index 0000000..21b9faf --- /dev/null +++ b/src/audio/gsm/src/lpc.cpp @@ -0,0 +1,341 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/* $Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/lpc.c,v 1.5 1994/12/30 23:14:54 jutta Exp $ */ + +#include +#include + +#include "private.h" + +#include "gsm.h" +#include "proto.h" + +#undef P + +/* + * 4.2.4 .. 4.2.7 LPC ANALYSIS SECTION + */ + +/* 4.2.4 */ + + +static void Autocorrelation P2((s, L_ACF), + word * s, /* [0..159] IN/OUT */ + longword * L_ACF) /* [0..8] OUT */ +/* + * The goal is to compute the array L_ACF[k]. The signal s[i] must + * be scaled in order to avoid an overflow situation. + */ +{ + register int k, i; + + word temp, smax, scalauto; + +#ifdef USE_FLOAT_MUL + float float_s[160]; +#endif + + /* Dynamic scaling of the array s[0..159] + */ + + /* Search for the maximum. + */ + smax = 0; + for (k = 0; k <= 159; k++) { + temp = GSM_ABS( s[k] ); + if (temp > smax) smax = temp; + } + + /* Computation of the scaling factor. + */ + if (smax == 0) scalauto = 0; + else { + assert(smax > 0); + scalauto = 4 - gsm_norm( (longword)smax << 16 );/* sub(4,..) */ + } + + /* Scaling of the array s[0...159] + */ + + if (scalauto > 0) { + +# ifdef USE_FLOAT_MUL +# define SCALE(n) \ + case n: for (k = 0; k <= 159; k++) \ + float_s[k] = (float) \ + (s[k] = GSM_MULT_R(s[k], 16384 >> (n-1)));\ + break; +# else +# define SCALE(n) \ + case n: for (k = 0; k <= 159; k++) \ + s[k] = GSM_MULT_R( s[k], 16384 >> (n-1) );\ + break; +# endif /* USE_FLOAT_MUL */ + + switch (scalauto) { + SCALE(1) + SCALE(2) + SCALE(3) + SCALE(4) + } +# undef SCALE + } +# ifdef USE_FLOAT_MUL + else for (k = 0; k <= 159; k++) float_s[k] = (float) s[k]; +# endif + + /* Compute the L_ACF[..]. + */ + { +# ifdef USE_FLOAT_MUL + register float * sp = float_s; + register float sl = *sp; + +# define STEP(k) L_ACF[k] += (longword)(sl * sp[ -(k) ]); +# else + word * sp = s; + word sl = *sp; + +# define STEP(k) L_ACF[k] += ((longword)sl * sp[ -(k) ]); +# endif + +# define NEXTI sl = *++sp + + + for (k = 9; k--; L_ACF[k] = 0) ; + + STEP (0); + NEXTI; + STEP(0); STEP(1); + NEXTI; + STEP(0); STEP(1); STEP(2); + NEXTI; + STEP(0); STEP(1); STEP(2); STEP(3); + NEXTI; + STEP(0); STEP(1); STEP(2); STEP(3); STEP(4); + NEXTI; + STEP(0); STEP(1); STEP(2); STEP(3); STEP(4); STEP(5); + NEXTI; + STEP(0); STEP(1); STEP(2); STEP(3); STEP(4); STEP(5); STEP(6); + NEXTI; + STEP(0); STEP(1); STEP(2); STEP(3); STEP(4); STEP(5); STEP(6); STEP(7); + + for (i = 8; i <= 159; i++) { + + NEXTI; + + STEP(0); + STEP(1); STEP(2); STEP(3); STEP(4); + STEP(5); STEP(6); STEP(7); STEP(8); + } + + for (k = 9; k--; L_ACF[k] <<= 1) ; + + } + /* Rescaling of the array s[0..159] + */ + if (scalauto > 0) { + assert(scalauto <= 4); + for (k = 160; k--; *s++ <<= scalauto) ; + } +} + +#if defined(USE_FLOAT_MUL) && defined(FAST) + +static void Fast_Autocorrelation P2((s, L_ACF), + word * s, /* [0..159] IN/OUT */ + longword * L_ACF) /* [0..8] OUT */ +{ + register int k, i; + float f_L_ACF[9]; + float scale; + + float s_f[160]; + register float *sf = s_f; + + for (i = 0; i < 160; ++i) sf[i] = s[i]; + for (k = 0; k <= 8; k++) { + register float L_temp2 = 0; + register float *sfl = sf - k; + for (i = k; i < 160; ++i) L_temp2 += sf[i] * sfl[i]; + f_L_ACF[k] = L_temp2; + } + scale = MAX_LONGWORD / f_L_ACF[0]; + + for (k = 0; k <= 8; k++) { + L_ACF[k] = f_L_ACF[k] * scale; + } +} +#endif /* defined (USE_FLOAT_MUL) && defined (FAST) */ + +/* 4.2.5 */ + +static void Reflection_coefficients P2( (L_ACF, r), + longword * L_ACF, /* 0...8 IN */ + register word * r /* 0...7 OUT */ +) +{ + register int i, m, n; + register word temp; + register longword ltmp; + word ACF[9]; /* 0..8 */ + word P[ 9]; /* 0..8 */ + word K[ 9]; /* 2..8 */ + + /* Schur recursion with 16 bits arithmetic. + */ + + if (L_ACF[0] == 0) { + for (i = 8; i--; *r++ = 0) ; + return; + } + + assert( L_ACF[0] != 0 ); + temp = gsm_norm( L_ACF[0] ); + + assert(temp >= 0 && temp < 32); + + /* ? overflow ? */ + for (i = 0; i <= 8; i++) ACF[i] = SASR( L_ACF[i] << temp, 16 ); + + /* Initialize array P[..] and K[..] for the recursion. + */ + + for (i = 1; i <= 7; i++) K[ i ] = ACF[ i ]; + for (i = 0; i <= 8; i++) P[ i ] = ACF[ i ]; + + /* Compute reflection coefficients + */ + for (n = 1; n <= 8; n++, r++) { + + temp = P[1]; + temp = GSM_ABS(temp); + if (P[0] < temp) { + for (i = n; i <= 8; i++) *r++ = 0; + return; + } + + *r = gsm_div( temp, P[0] ); + + assert(*r >= 0); + if (P[1] > 0) *r = -*r; /* r[n] = sub(0, r[n]) */ + assert (*r != MIN_WORD); + if (n == 8) return; + + /* Schur recursion + */ + temp = GSM_MULT_R( P[1], *r ); + P[0] = GSM_ADD( P[0], temp ); + + for (m = 1; m <= 8 - n; m++) { + temp = GSM_MULT_R( K[ m ], *r ); + P[m] = GSM_ADD( P[ m+1 ], temp ); + + temp = GSM_MULT_R( P[ m+1 ], *r ); + K[m] = GSM_ADD( K[ m ], temp ); + } + } +} + +/* 4.2.6 */ + +static void Transformation_to_Log_Area_Ratios P1((r), + register word * r /* 0..7 IN/OUT */ +) +/* + * The following scaling for r[..] and LAR[..] has been used: + * + * r[..] = integer( real_r[..]*32768. ); -1 <= real_r < 1. + * LAR[..] = integer( real_LAR[..] * 16384 ); + * with -1.625 <= real_LAR <= 1.625 + */ +{ + register word temp; + register int i; + + + /* Computation of the LAR[0..7] from the r[0..7] + */ + for (i = 1; i <= 8; i++, r++) { + + temp = *r; + temp = GSM_ABS(temp); + assert(temp >= 0); + + if (temp < 22118) { + temp >>= 1; + } else if (temp < 31130) { + assert( temp >= 11059 ); + temp -= 11059; + } else { + assert( temp >= 26112 ); + temp -= 26112; + temp <<= 2; + } + + *r = *r < 0 ? -temp : temp; + assert( *r != MIN_WORD ); + } +} + +/* 4.2.7 */ + +static void Quantization_and_coding P1((LAR), + register word * LAR /* [0..7] IN/OUT */ +) +{ + register word temp; + longword ltmp; + + + /* This procedure needs four tables; the following equations + * give the optimum scaling for the constants: + * + * A[0..7] = integer( real_A[0..7] * 1024 ) + * B[0..7] = integer( real_B[0..7] * 512 ) + * MAC[0..7] = maximum of the LARc[0..7] + * MIC[0..7] = minimum of the LARc[0..7] + */ + +# undef STEP +# define STEP( A, B, MAC, MIC ) \ + temp = GSM_MULT( A, *LAR ); \ + temp = GSM_ADD( temp, B ); \ + temp = GSM_ADD( temp, 256 ); \ + temp = SASR( temp, 9 ); \ + *LAR = temp>MAC ? MAC - MIC : (tempfast) Fast_Autocorrelation (s, L_ACF ); + else +#endif + Autocorrelation (s, L_ACF ); + Reflection_coefficients (L_ACF, LARc ); + Transformation_to_Log_Area_Ratios (LARc); + Quantization_and_coding (LARc); +} diff --git a/src/audio/gsm/src/preprocess.cpp b/src/audio/gsm/src/preprocess.cpp new file mode 100644 index 0000000..fa16efe --- /dev/null +++ b/src/audio/gsm/src/preprocess.cpp @@ -0,0 +1,113 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/* $Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/preprocess.c,v 1.2 1994/05/10 20:18:45 jutta Exp $ */ + +#include +#include + +#include "private.h" + +#include "gsm.h" +#include "proto.h" + +/* 4.2.0 .. 4.2.3 PREPROCESSING SECTION + * + * After A-law to linear conversion (or directly from the + * Ato D converter) the following scaling is assumed for + * input to the RPE-LTP algorithm: + * + * in: 0.1.....................12 + * S.v.v.v.v.v.v.v.v.v.v.v.v.*.*.* + * + * Where S is the sign bit, v a valid bit, and * a "don't care" bit. + * The original signal is called sop[..] + * + * out: 0.1................... 12 + * S.S.v.v.v.v.v.v.v.v.v.v.v.v.0.0 + */ + + +void Gsm_Preprocess P3((S, s, so), + struct gsm_state * S, + word * s, + word * so ) /* [0..159] IN/OUT */ +{ + + word z1 = S->z1; + longword L_z2 = S->L_z2; + word mp = S->mp; + + word s1; + longword L_s2; + + longword L_temp; + + word msp, lsp; + word SO; + + longword ltmp; /* for ADD */ + ulongword utmp; /* for L_ADD */ + + register int k = 160; + + while (k--) { + + /* 4.2.1 Downscaling of the input signal + */ + SO = SASR( *s, 3 ) << 2; + s++; + + assert (SO >= -0x4000); /* downscaled by */ + assert (SO <= 0x3FFC); /* previous routine. */ + + + /* 4.2.2 Offset compensation + * + * This part implements a high-pass filter and requires extended + * arithmetic precision for the recursive part of this filter. + * The input of this procedure is the array so[0...159] and the + * output the array sof[ 0...159 ]. + */ + /* Compute the non-recursive part + */ + + s1 = SO - z1; /* s1 = gsm_sub( *so, z1 ); */ + z1 = SO; + + assert(s1 != MIN_WORD); + + /* Compute the recursive part + */ + L_s2 = s1; + L_s2 <<= 15; + + /* Execution of a 31 bv 16 bits multiplication + */ + + msp = SASR( L_z2, 15 ); + lsp = L_z2-((longword)msp<<15); /* gsm_L_sub(L_z2,(msp<<15)); */ + + L_s2 += GSM_MULT_R( lsp, 32735 ); + L_temp = (longword)msp * 32735; /* GSM_L_MULT(msp,32735) >> 1;*/ + L_z2 = GSM_L_ADD( L_temp, L_s2 ); + + /* Compute sof[k] with rounding + */ + L_temp = GSM_L_ADD( L_z2, 16384 ); + + /* 4.2.3 Preemphasis + */ + + msp = GSM_MULT_R( mp, -28180 ); + mp = SASR( L_temp, 15 ); + *so++ = GSM_ADD( mp, msp ); + } + + S->z1 = z1; + S->L_z2 = L_z2; + S->mp = mp; +} diff --git a/src/audio/gsm/src/rpe.cpp b/src/audio/gsm/src/rpe.cpp new file mode 100644 index 0000000..eda4f18 --- /dev/null +++ b/src/audio/gsm/src/rpe.cpp @@ -0,0 +1,488 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/* $Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/rpe.c,v 1.3 1994/05/10 20:18:46 jutta Exp $ */ + +#include +#include + +#include "private.h" + +#include "gsm.h" +#include "proto.h" + +/* 4.2.13 .. 4.2.17 RPE ENCODING SECTION + */ + +/* 4.2.13 */ + +static void Weighting_filter P2((e, x), + register word * e, /* signal [-5..0.39.44] IN */ + word * x /* signal [0..39] OUT */ +) +/* + * The coefficients of the weighting filter are stored in a table + * (see table 4.4). The following scaling is used: + * + * H[0..10] = integer( real_H[ 0..10] * 8192 ); + */ +{ + /* word wt[ 50 ]; */ + + register longword L_result; + register int k /* , i */ ; + + /* Initialization of a temporary working array wt[0...49] + */ + + /* for (k = 0; k <= 4; k++) wt[k] = 0; + * for (k = 5; k <= 44; k++) wt[k] = *e++; + * for (k = 45; k <= 49; k++) wt[k] = 0; + * + * (e[-5..-1] and e[40..44] are allocated by the caller, + * are initially zero and are not written anywhere.) + */ + e -= 5; + + /* Compute the signal x[0..39] + */ + for (k = 0; k <= 39; k++) { + + L_result = 8192 >> 1; + + /* for (i = 0; i <= 10; i++) { + * L_temp = GSM_L_MULT( wt[k+i], gsm_H[i] ); + * L_result = GSM_L_ADD( L_result, L_temp ); + * } + */ + +#undef STEP +#define STEP( i, H ) (e[ k + i ] * (longword)H) + + /* Every one of these multiplications is done twice -- + * but I don't see an elegant way to optimize this. + * Do you? + */ + +#ifdef STUPID_COMPILER + L_result += STEP( 0, -134 ) ; + L_result += STEP( 1, -374 ) ; + /* + STEP( 2, 0 ) */ + L_result += STEP( 3, 2054 ) ; + L_result += STEP( 4, 5741 ) ; + L_result += STEP( 5, 8192 ) ; + L_result += STEP( 6, 5741 ) ; + L_result += STEP( 7, 2054 ) ; + /* + STEP( 8, 0 ) */ + L_result += STEP( 9, -374 ) ; + L_result += STEP( 10, -134 ) ; +#else + L_result += + STEP( 0, -134 ) + + STEP( 1, -374 ) + /* + STEP( 2, 0 ) */ + + STEP( 3, 2054 ) + + STEP( 4, 5741 ) + + STEP( 5, 8192 ) + + STEP( 6, 5741 ) + + STEP( 7, 2054 ) + /* + STEP( 8, 0 ) */ + + STEP( 9, -374 ) + + STEP(10, -134 ) + ; +#endif + + /* L_result = GSM_L_ADD( L_result, L_result ); (* scaling(x2) *) + * L_result = GSM_L_ADD( L_result, L_result ); (* scaling(x4) *) + * + * x[k] = SASR( L_result, 16 ); + */ + + /* 2 adds vs. >>16 => 14, minus one shift to compensate for + * those we lost when replacing L_MULT by '*'. + */ + + L_result = SASR( L_result, 13 ); + x[k] = ( L_result < MIN_WORD ? MIN_WORD + : (L_result > MAX_WORD ? MAX_WORD : L_result )); + } +} + +/* 4.2.14 */ + +static void RPE_grid_selection P3((x,xM,Mc_out), + word * x, /* [0..39] IN */ + word * xM, /* [0..12] OUT */ + word * Mc_out /* OUT */ +) +/* + * The signal x[0..39] is used to select the RPE grid which is + * represented by Mc. + */ +{ + /* register word temp1; */ + register int /* m, */ i; + register longword L_result, L_temp; + longword EM; /* xxx should be L_EM? */ + word Mc; + + longword L_common_0_3; + + EM = 0; + Mc = 0; + + /* for (m = 0; m <= 3; m++) { + * L_result = 0; + * + * + * for (i = 0; i <= 12; i++) { + * + * temp1 = SASR( x[m + 3*i], 2 ); + * + * assert(temp1 != MIN_WORD); + * + * L_temp = GSM_L_MULT( temp1, temp1 ); + * L_result = GSM_L_ADD( L_temp, L_result ); + * } + * + * if (L_result > EM) { + * Mc = m; + * EM = L_result; + * } + * } + */ + +#undef STEP +#define STEP( m, i ) L_temp = SASR( x[m + 3 * i], 2 ); \ + L_result += L_temp * L_temp; + + /* common part of 0 and 3 */ + + L_result = 0; + STEP( 0, 1 ); STEP( 0, 2 ); STEP( 0, 3 ); STEP( 0, 4 ); + STEP( 0, 5 ); STEP( 0, 6 ); STEP( 0, 7 ); STEP( 0, 8 ); + STEP( 0, 9 ); STEP( 0, 10); STEP( 0, 11); STEP( 0, 12); + L_common_0_3 = L_result; + + /* i = 0 */ + + STEP( 0, 0 ); + L_result <<= 1; /* implicit in L_MULT */ + EM = L_result; + + /* i = 1 */ + + L_result = 0; + STEP( 1, 0 ); + STEP( 1, 1 ); STEP( 1, 2 ); STEP( 1, 3 ); STEP( 1, 4 ); + STEP( 1, 5 ); STEP( 1, 6 ); STEP( 1, 7 ); STEP( 1, 8 ); + STEP( 1, 9 ); STEP( 1, 10); STEP( 1, 11); STEP( 1, 12); + L_result <<= 1; + if (L_result > EM) { + Mc = 1; + EM = L_result; + } + + /* i = 2 */ + + L_result = 0; + STEP( 2, 0 ); + STEP( 2, 1 ); STEP( 2, 2 ); STEP( 2, 3 ); STEP( 2, 4 ); + STEP( 2, 5 ); STEP( 2, 6 ); STEP( 2, 7 ); STEP( 2, 8 ); + STEP( 2, 9 ); STEP( 2, 10); STEP( 2, 11); STEP( 2, 12); + L_result <<= 1; + if (L_result > EM) { + Mc = 2; + EM = L_result; + } + + /* i = 3 */ + + L_result = L_common_0_3; + STEP( 3, 12 ); + L_result <<= 1; + if (L_result > EM) { + Mc = 3; + EM = L_result; + } + + /**/ + + /* Down-sampling by a factor 3 to get the selected xM[0..12] + * RPE sequence. + */ + for (i = 0; i <= 12; i ++) xM[i] = x[Mc + 3*i]; + *Mc_out = Mc; +} + +/* 4.12.15 */ + +static void APCM_quantization_xmaxc_to_exp_mant P3((xmaxc,exp_out,mant_out), + word xmaxc, /* IN */ + word * exp_out, /* OUT */ + word * mant_out ) /* OUT */ +{ + word exp, mant; + + /* Compute exponent and mantissa of the decoded version of xmaxc + */ + + exp = 0; + if (xmaxc > 15) exp = SASR(xmaxc, 3) - 1; + mant = xmaxc - (exp << 3); + + if (mant == 0) { + exp = -4; + mant = 7; + } + else { + while (mant <= 7) { + mant = mant << 1 | 1; + exp--; + } + mant -= 8; + } + + assert( exp >= -4 && exp <= 6 ); + assert( mant >= 0 && mant <= 7 ); + + *exp_out = exp; + *mant_out = mant; +} + +static void APCM_quantization P5((xM,xMc,mant_out,exp_out,xmaxc_out), + word * xM, /* [0..12] IN */ + + word * xMc, /* [0..12] OUT */ + word * mant_out, /* OUT */ + word * exp_out, /* OUT */ + word * xmaxc_out /* OUT */ +) +{ + int i, itest; + + word xmax, xmaxc, temp, temp1, temp2; + word exp, mant; + + + /* Find the maximum absolute value xmax of xM[0..12]. + */ + + xmax = 0; + for (i = 0; i <= 12; i++) { + temp = xM[i]; + temp = GSM_ABS(temp); + if (temp > xmax) xmax = temp; + } + + /* Qantizing and coding of xmax to get xmaxc. + */ + + exp = 0; + temp = SASR( xmax, 9 ); + itest = 0; + + for (i = 0; i <= 5; i++) { + + itest |= (temp <= 0); + temp = SASR( temp, 1 ); + + assert(exp <= 5); + if (itest == 0) exp++; /* exp = add (exp, 1) */ + } + + assert(exp <= 6 && exp >= 0); + temp = exp + 5; + + assert(temp <= 11 && temp >= 0); + xmaxc = gsm_add( SASR(xmax, temp), exp << 3 ); + + /* Quantizing and coding of the xM[0..12] RPE sequence + * to get the xMc[0..12] + */ + + APCM_quantization_xmaxc_to_exp_mant( xmaxc, &exp, &mant ); + + /* This computation uses the fact that the decoded version of xmaxc + * can be calculated by using the exponent and the mantissa part of + * xmaxc (logarithmic table). + * So, this method avoids any division and uses only a scaling + * of the RPE samples by a function of the exponent. A direct + * multiplication by the inverse of the mantissa (NRFAC[0..7] + * found in table 4.5) gives the 3 bit coded version xMc[0..12] + * of the RPE samples. + */ + + + /* Direct computation of xMc[0..12] using table 4.5 + */ + + assert( exp <= 4096 && exp >= -4096); + assert( mant >= 0 && mant <= 7 ); + + temp1 = 6 - exp; /* normalization by the exponent */ + temp2 = gsm_NRFAC[ mant ]; /* inverse mantissa */ + + for (i = 0; i <= 12; i++) { + + assert(temp1 >= 0 && temp1 < 16); + + temp = xM[i] << temp1; + temp = GSM_MULT( temp, temp2 ); + temp = SASR(temp, 12); + xMc[i] = temp + 4; /* see note below */ + } + + /* NOTE: This equation is used to make all the xMc[i] positive. + */ + + *mant_out = mant; + *exp_out = exp; + *xmaxc_out = xmaxc; +} + +/* 4.2.16 */ + +static void APCM_inverse_quantization P4((xMc,mant,exp,xMp), + register word * xMc, /* [0..12] IN */ + word mant, + word exp, + register word * xMp) /* [0..12] OUT */ +/* + * This part is for decoding the RPE sequence of coded xMc[0..12] + * samples to obtain the xMp[0..12] array. Table 4.6 is used to get + * the mantissa of xmaxc (FAC[0..7]). + */ +{ + int i; + word temp, temp1, temp2, temp3; + longword ltmp; + + assert( mant >= 0 && mant <= 7 ); + + temp1 = gsm_FAC[ mant ]; /* see 4.2-15 for mant */ + temp2 = gsm_sub( 6, exp ); /* see 4.2-15 for exp */ + temp3 = gsm_asl( 1, gsm_sub( temp2, 1 )); + + for (i = 13; i--;) { + + assert( *xMc <= 7 && *xMc >= 0 ); /* 3 bit unsigned */ + + /* temp = gsm_sub( *xMc++ << 1, 7 ); */ + temp = (*xMc++ << 1) - 7; /* restore sign */ + assert( temp <= 7 && temp >= -7 ); /* 4 bit signed */ + + temp <<= 12; /* 16 bit signed */ + temp = GSM_MULT_R( temp1, temp ); + temp = GSM_ADD( temp, temp3 ); + *xMp++ = gsm_asr( temp, temp2 ); + } +} + +/* 4.2.17 */ + +static void RPE_grid_positioning P3((Mc,xMp,ep), + word Mc, /* grid position IN */ + register word * xMp, /* [0..12] IN */ + register word * ep /* [0..39] OUT */ +) +/* + * This procedure computes the reconstructed long term residual signal + * ep[0..39] for the LTP analysis filter. The inputs are the Mc + * which is the grid position selection and the xMp[0..12] decoded + * RPE samples which are upsampled by a factor of 3 by inserting zero + * values. + */ +{ + int i = 13; + + assert(0 <= Mc && Mc <= 3); + + switch (Mc) { + case 3: *ep++ = 0; + case 2: do { + *ep++ = 0; + case 1: *ep++ = 0; + case 0: *ep++ = *xMp++; + } while (--i); + } + while (++Mc < 4) *ep++ = 0; + + /* + + int i, k; + for (k = 0; k <= 39; k++) ep[k] = 0; + for (i = 0; i <= 12; i++) { + ep[ Mc + (3*i) ] = xMp[i]; + } + */ +} + +/* 4.2.18 */ + +/* This procedure adds the reconstructed long term residual signal + * ep[0..39] to the estimated signal dpp[0..39] from the long term + * analysis filter to compute the reconstructed short term residual + * signal dp[-40..-1]; also the reconstructed short term residual + * array dp[-120..-41] is updated. + */ + +#if 0 /* Has been inlined in code.c */ +void Gsm_Update_of_reconstructed_short_time_residual_signal P3((dpp, ep, dp), + word * dpp, /* [0...39] IN */ + word * ep, /* [0...39] IN */ + word * dp) /* [-120...-1] IN/OUT */ +{ + int k; + + for (k = 0; k <= 79; k++) + dp[ -120 + k ] = dp[ -80 + k ]; + + for (k = 0; k <= 39; k++) + dp[ -40 + k ] = gsm_add( ep[k], dpp[k] ); +} +#endif /* Has been inlined in code.c */ + +void Gsm_RPE_Encoding P5((S,e,xmaxc,Mc,xMc), + + struct gsm_state * S, + + word * e, /* -5..-1][0..39][40..44 IN/OUT */ + word * xmaxc, /* OUT */ + word * Mc, /* OUT */ + word * xMc) /* [0..12] OUT */ +{ + word x[40]; + word xM[13], xMp[13]; + word mant, exp; + + Weighting_filter(e, x); + RPE_grid_selection(x, xM, Mc); + + APCM_quantization( xM, xMc, &mant, &exp, xmaxc); + APCM_inverse_quantization( xMc, mant, exp, xMp); + + RPE_grid_positioning( *Mc, xMp, e ); + +} + +void Gsm_RPE_Decoding P5((S, xmaxcr, Mcr, xMcr, erp), + struct gsm_state * S, + + word xmaxcr, + word Mcr, + word * xMcr, /* [0..12], 3 bits IN */ + word * erp /* [0..39] OUT */ +) +{ + word exp, mant; + word xMp[ 13 ]; + + APCM_quantization_xmaxc_to_exp_mant( xmaxcr, &exp, &mant ); + APCM_inverse_quantization( xMcr, mant, exp, xMp ); + RPE_grid_positioning( Mcr, xMp, erp ); + +} diff --git a/src/audio/gsm/src/short_term.cpp b/src/audio/gsm/src/short_term.cpp new file mode 100644 index 0000000..f7829ba --- /dev/null +++ b/src/audio/gsm/src/short_term.cpp @@ -0,0 +1,429 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/* $Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/short_term.c,v 1.2 1994/05/10 20:18:47 jutta Exp $ */ + +#include +#include + +#include "private.h" + +#include "gsm.h" +#include "proto.h" + +/* + * SHORT TERM ANALYSIS FILTERING SECTION + */ + +/* 4.2.8 */ + +static void Decoding_of_the_coded_Log_Area_Ratios P2((LARc,LARpp), + word * LARc, /* coded log area ratio [0..7] IN */ + word * LARpp) /* out: decoded .. */ +{ + register word temp1 /* , temp2 */; + register long ltmp; /* for GSM_ADD */ + + /* This procedure requires for efficient implementation + * two tables. + * + * INVA[1..8] = integer( (32768 * 8) / real_A[1..8]) + * MIC[1..8] = minimum value of the LARc[1..8] + */ + + /* Compute the LARpp[1..8] + */ + + /* for (i = 1; i <= 8; i++, B++, MIC++, INVA++, LARc++, LARpp++) { + * + * temp1 = GSM_ADD( *LARc, *MIC ) << 10; + * temp2 = *B << 1; + * temp1 = GSM_SUB( temp1, temp2 ); + * + * assert(*INVA != MIN_WORD); + * + * temp1 = GSM_MULT_R( *INVA, temp1 ); + * *LARpp = GSM_ADD( temp1, temp1 ); + * } + */ + +#undef STEP +#define STEP( B, MIC, INVA ) \ + temp1 = GSM_ADD( *LARc++, MIC ) << 10; \ + temp1 = GSM_SUB( temp1, B << 1 ); \ + temp1 = GSM_MULT_R( INVA, temp1 ); \ + *LARpp++ = GSM_ADD( temp1, temp1 ); + + STEP( 0, -32, 13107 ); + STEP( 0, -32, 13107 ); + STEP( 2048, -16, 13107 ); + STEP( -2560, -16, 13107 ); + + STEP( 94, -8, 19223 ); + STEP( -1792, -8, 17476 ); + STEP( -341, -4, 31454 ); + STEP( -1144, -4, 29708 ); + + /* NOTE: the addition of *MIC is used to restore + * the sign of *LARc. + */ +} + +/* 4.2.9 */ +/* Computation of the quantized reflection coefficients + */ + +/* 4.2.9.1 Interpolation of the LARpp[1..8] to get the LARp[1..8] + */ + +/* + * Within each frame of 160 analyzed speech samples the short term + * analysis and synthesis filters operate with four different sets of + * coefficients, derived from the previous set of decoded LARs(LARpp(j-1)) + * and the actual set of decoded LARs (LARpp(j)) + * + * (Initial value: LARpp(j-1)[1..8] = 0.) + */ + +static void Coefficients_0_12 P3((LARpp_j_1, LARpp_j, LARp), + register word * LARpp_j_1, + register word * LARpp_j, + register word * LARp) +{ + register int i; + register longword ltmp; + + for (i = 1; i <= 8; i++, LARp++, LARpp_j_1++, LARpp_j++) { + *LARp = GSM_ADD( SASR( *LARpp_j_1, 2 ), SASR( *LARpp_j, 2 )); + *LARp = GSM_ADD( *LARp, SASR( *LARpp_j_1, 1)); + } +} + +static void Coefficients_13_26 P3((LARpp_j_1, LARpp_j, LARp), + register word * LARpp_j_1, + register word * LARpp_j, + register word * LARp) +{ + register int i; + register longword ltmp; + for (i = 1; i <= 8; i++, LARpp_j_1++, LARpp_j++, LARp++) { + *LARp = GSM_ADD( SASR( *LARpp_j_1, 1), SASR( *LARpp_j, 1 )); + } +} + +static void Coefficients_27_39 P3((LARpp_j_1, LARpp_j, LARp), + register word * LARpp_j_1, + register word * LARpp_j, + register word * LARp) +{ + register int i; + register longword ltmp; + + for (i = 1; i <= 8; i++, LARpp_j_1++, LARpp_j++, LARp++) { + *LARp = GSM_ADD( SASR( *LARpp_j_1, 2 ), SASR( *LARpp_j, 2 )); + *LARp = GSM_ADD( *LARp, SASR( *LARpp_j, 1 )); + } +} + + +static void Coefficients_40_159 P2((LARpp_j, LARp), + register word * LARpp_j, + register word * LARp) +{ + register int i; + + for (i = 1; i <= 8; i++, LARp++, LARpp_j++) + *LARp = *LARpp_j; +} + +/* 4.2.9.2 */ + +static void LARp_to_rp P1((LARp), + register word * LARp) /* [0..7] IN/OUT */ +/* + * The input of this procedure is the interpolated LARp[0..7] array. + * The reflection coefficients, rp[i], are used in the analysis + * filter and in the synthesis filter. + */ +{ + register int i; + register word temp; + register longword ltmp; + + for (i = 1; i <= 8; i++, LARp++) { + + /* temp = GSM_ABS( *LARp ); + * + * if (temp < 11059) temp <<= 1; + * else if (temp < 20070) temp += 11059; + * else temp = GSM_ADD( temp >> 2, 26112 ); + * + * *LARp = *LARp < 0 ? -temp : temp; + */ + + if (*LARp < 0) { + temp = *LARp == MIN_WORD ? MAX_WORD : -(*LARp); + *LARp = - ((temp < 11059) ? temp << 1 + : ((temp < 20070) ? temp + 11059 + : GSM_ADD( temp >> 2, 26112 ))); + } else { + temp = *LARp; + *LARp = (temp < 11059) ? temp << 1 + : ((temp < 20070) ? temp + 11059 + : GSM_ADD( temp >> 2, 26112 )); + } + } +} + + +/* 4.2.10 */ +static void Short_term_analysis_filtering P4((S,rp,k_n,s), + struct gsm_state * S, + register word * rp, /* [0..7] IN */ + register int k_n, /* k_end - k_start */ + register word * s /* [0..n-1] IN/OUT */ +) +/* + * This procedure computes the short term residual signal d[..] to be fed + * to the RPE-LTP loop from the s[..] signal and from the local rp[..] + * array (quantized reflection coefficients). As the call of this + * procedure can be done in many ways (see the interpolation of the LAR + * coefficient), it is assumed that the computation begins with index + * k_start (for arrays d[..] and s[..]) and stops with index k_end + * (k_start and k_end are defined in 4.2.9.1). This procedure also + * needs to keep the array u[0..7] in memory for each call. + */ +{ + register word * u = S->u; + register int i; + register word di, zzz, ui, sav, rpi; + register longword ltmp; + + for (; k_n--; s++) { + + di = sav = *s; + + for (i = 0; i < 8; i++) { /* YYY */ + + ui = u[i]; + rpi = rp[i]; + u[i] = sav; + + zzz = GSM_MULT_R(rpi, di); + sav = GSM_ADD( ui, zzz); + + zzz = GSM_MULT_R(rpi, ui); + di = GSM_ADD( di, zzz ); + } + + *s = di; + } +} + +#if defined(USE_FLOAT_MUL) && defined(FAST) + +static void Fast_Short_term_analysis_filtering P4((S,rp,k_n,s), + struct gsm_state * S, + register word * rp, /* [0..7] IN */ + register int k_n, /* k_end - k_start */ + register word * s /* [0..n-1] IN/OUT */ +) +{ + register word * u = S->u; + register int i; + + float uf[8], + rpf[8]; + + register float scalef = 3.0517578125e-5; + register float sav, di, temp; + + for (i = 0; i < 8; ++i) { + uf[i] = u[i]; + rpf[i] = rp[i] * scalef; + } + for (; k_n--; s++) { + sav = di = *s; + for (i = 0; i < 8; ++i) { + register float rpfi = rpf[i]; + register float ufi = uf[i]; + + uf[i] = sav; + temp = rpfi * di + ufi; + di += rpfi * ufi; + sav = temp; + } + *s = di; + } + for (i = 0; i < 8; ++i) u[i] = uf[i]; +} +#endif /* ! (defined (USE_FLOAT_MUL) && defined (FAST)) */ + +static void Short_term_synthesis_filtering P5((S,rrp,k,wt,sr), + struct gsm_state * S, + register word * rrp, /* [0..7] IN */ + register int k, /* k_end - k_start */ + register word * wt, /* [0..k-1] IN */ + register word * sr /* [0..k-1] OUT */ +) +{ + register word * v = S->v; + register int i; + register word sri, tmp1, tmp2; + register longword ltmp; /* for GSM_ADD & GSM_SUB */ + + while (k--) { + sri = *wt++; + for (i = 8; i--;) { + + /* sri = GSM_SUB( sri, gsm_mult_r( rrp[i], v[i] ) ); + */ + tmp1 = rrp[i]; + tmp2 = v[i]; + tmp2 = ( tmp1 == MIN_WORD && tmp2 == MIN_WORD + ? MAX_WORD + : 0x0FFFF & (( (longword)tmp1 * (longword)tmp2 + + 16384) >> 15)) ; + + sri = GSM_SUB( sri, tmp2 ); + + /* v[i+1] = GSM_ADD( v[i], gsm_mult_r( rrp[i], sri ) ); + */ + tmp1 = ( tmp1 == MIN_WORD && sri == MIN_WORD + ? MAX_WORD + : 0x0FFFF & (( (longword)tmp1 * (longword)sri + + 16384) >> 15)) ; + + v[i+1] = GSM_ADD( v[i], tmp1); + } + *sr++ = v[0] = sri; + } +} + + +#if defined(FAST) && defined(USE_FLOAT_MUL) + +static void Fast_Short_term_synthesis_filtering P5((S,rrp,k,wt,sr), + struct gsm_state * S, + register word * rrp, /* [0..7] IN */ + register int k, /* k_end - k_start */ + register word * wt, /* [0..k-1] IN */ + register word * sr /* [0..k-1] OUT */ +) +{ + register word * v = S->v; + register int i; + + float va[9], rrpa[8]; + register float scalef = 3.0517578125e-5, temp; + + for (i = 0; i < 8; ++i) { + va[i] = v[i]; + rrpa[i] = (float)rrp[i] * scalef; + } + while (k--) { + register float sri = *wt++; + for (i = 8; i--;) { + sri -= rrpa[i] * va[i]; + if (sri < -32768.) sri = -32768.; + else if (sri > 32767.) sri = 32767.; + + temp = va[i] + rrpa[i] * sri; + if (temp < -32768.) temp = -32768.; + else if (temp > 32767.) temp = 32767.; + va[i+1] = temp; + } + *sr++ = va[0] = sri; + } + for (i = 0; i < 9; ++i) v[i] = va[i]; +} + +#endif /* defined(FAST) && defined(USE_FLOAT_MUL) */ + +void Gsm_Short_Term_Analysis_Filter P3((S,LARc,s), + + struct gsm_state * S, + + word * LARc, /* coded log area ratio [0..7] IN */ + word * s /* signal [0..159] IN/OUT */ +) +{ + word * LARpp_j = S->LARpp[ S->j ]; + word * LARpp_j_1 = S->LARpp[ S->j ^= 1 ]; + + word LARp[8]; + +#undef FILTER +#if defined(FAST) && defined(USE_FLOAT_MUL) +# define FILTER (* (S->fast \ + ? Fast_Short_term_analysis_filtering \ + : Short_term_analysis_filtering )) + +#else +# define FILTER Short_term_analysis_filtering +#endif + + Decoding_of_the_coded_Log_Area_Ratios( LARc, LARpp_j ); + + Coefficients_0_12( LARpp_j_1, LARpp_j, LARp ); + LARp_to_rp( LARp ); + FILTER( S, LARp, 13, s); + + Coefficients_13_26( LARpp_j_1, LARpp_j, LARp); + LARp_to_rp( LARp ); + FILTER( S, LARp, 14, s + 13); + + Coefficients_27_39( LARpp_j_1, LARpp_j, LARp); + LARp_to_rp( LARp ); + FILTER( S, LARp, 13, s + 27); + + Coefficients_40_159( LARpp_j, LARp); + LARp_to_rp( LARp ); + FILTER( S, LARp, 120, s + 40); +} + +void Gsm_Short_Term_Synthesis_Filter P4((S, LARcr, wt, s), + struct gsm_state * S, + + word * LARcr, /* received log area ratios [0..7] IN */ + word * wt, /* received d [0..159] IN */ + + word * s /* signal s [0..159] OUT */ +) +{ + word * LARpp_j = S->LARpp[ S->j ]; + word * LARpp_j_1 = S->LARpp[ S->j ^=1 ]; + + word LARp[8]; + +#undef FILTER +#if defined(FAST) && defined(USE_FLOAT_MUL) + +# define FILTER (* (S->fast \ + ? Fast_Short_term_synthesis_filtering \ + : Short_term_synthesis_filtering )) +#else +# define FILTER Short_term_synthesis_filtering +#endif + + Decoding_of_the_coded_Log_Area_Ratios( LARcr, LARpp_j ); + + Coefficients_0_12( LARpp_j_1, LARpp_j, LARp ); + LARp_to_rp( LARp ); + FILTER( S, LARp, 13, wt, s ); + + Coefficients_13_26( LARpp_j_1, LARpp_j, LARp); + LARp_to_rp( LARp ); + FILTER( S, LARp, 14, wt + 13, s + 13 ); + + Coefficients_27_39( LARpp_j_1, LARpp_j, LARp); + LARp_to_rp( LARp ); + FILTER( S, LARp, 13, wt + 27, s + 27 ); + + Coefficients_40_159( LARpp_j, LARp ); + LARp_to_rp( LARp ); + FILTER(S, LARp, 120, wt + 40, s + 40); +} diff --git a/src/audio/gsm/src/table.cpp b/src/audio/gsm/src/table.cpp new file mode 100644 index 0000000..0ed6f70 --- /dev/null +++ b/src/audio/gsm/src/table.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 1992 by Jutta Degener and Carsten Bormann, Technische + * Universitaet Berlin. See the accompanying file "COPYRIGHT" for + * details. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + */ + +/* $Header: /tmp_amd/presto/export/kbs/jutta/src/gsm/RCS/table.c,v 1.1 1992/10/28 00:15:50 jutta Exp $ */ + +/* Most of these tables are inlined at their point of use. + */ + +/* 4.4 TABLES USED IN THE FIXED POINT IMPLEMENTATION OF THE RPE-LTP + * CODER AND DECODER + * + * (Most of them inlined, so watch out.) + */ + +#define GSM_TABLE_C +#include "private.h" +#include "gsm.h" + +/* Table 4.1 Quantization of the Log.-Area Ratios + */ +/* i 1 2 3 4 5 6 7 8 */ +word gsm_A[8] = {20480, 20480, 20480, 20480, 13964, 15360, 8534, 9036}; +word gsm_B[8] = { 0, 0, 2048, -2560, 94, -1792, -341, -1144}; +word gsm_MIC[8] = { -32, -32, -16, -16, -8, -8, -4, -4 }; +word gsm_MAC[8] = { 31, 31, 15, 15, 7, 7, 3, 3 }; + + +/* Table 4.2 Tabulation of 1/A[1..8] + */ +word gsm_INVA[8]={ 13107, 13107, 13107, 13107, 19223, 17476, 31454, 29708 }; + + +/* Table 4.3a Decision level of the LTP gain quantizer + */ +/* bc 0 1 2 3 */ +word gsm_DLB[4] = { 6554, 16384, 26214, 32767 }; + + +/* Table 4.3b Quantization levels of the LTP gain quantizer + */ +/* bc 0 1 2 3 */ +word gsm_QLB[4] = { 3277, 11469, 21299, 32767 }; + + +/* Table 4.4 Coefficients of the weighting filter + */ +/* i 0 1 2 3 4 5 6 7 8 9 10 */ +word gsm_H[11] = {-134, -374, 0, 2054, 5741, 8192, 5741, 2054, 0, -374, -134 }; + + +/* Table 4.5 Normalized inverse mantissa used to compute xM/xmax + */ +/* i 0 1 2 3 4 5 6 7 */ +word gsm_NRFAC[8] = { 29128, 26215, 23832, 21846, 20165, 18725, 17476, 16384 }; + + +/* Table 4.6 Normalized direct mantissa used to compute xM/xmax + */ +/* i 0 1 2 3 4 5 6 7 */ +word gsm_FAC[8] = { 18431, 20479, 22527, 24575, 26623, 28671, 30719, 32767 }; diff --git a/src/audio/media_buffer.cpp b/src/audio/media_buffer.cpp new file mode 100644 index 0000000..cc62605 --- /dev/null +++ b/src/audio/media_buffer.cpp @@ -0,0 +1,128 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/audio/media_buffer.h b/src/audio/media_buffer.h new file mode 100644 index 0000000..80658f6 --- /dev/null +++ b/src/audio/media_buffer.h @@ -0,0 +1,73 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/audio/rtp_telephone_event.cpp b/src/audio/rtp_telephone_event.cpp new file mode 100644 index 0000000..a076531 --- /dev/null +++ b/src/audio/rtp_telephone_event.cpp @@ -0,0 +1,83 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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); +} + +unsigned char 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); +} + +char dtmf_ev2char(unsigned char 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); +} + diff --git a/src/audio/rtp_telephone_event.h b/src/audio/rtp_telephone_event.h new file mode 100644 index 0000000..372504c --- /dev/null +++ b/src/audio/rtp_telephone_event.h @@ -0,0 +1,79 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 +#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 VALID_DTMF_EV(ev) ( (ev) <= TEL_EV_DTMF_D ) +#define VALID_DTMF_SYM(s) ( ((s) >= '0' && (s) <= '9') || \ + ((s) >= 'a' && (s) <= 'd') || \ + ((s) >= 'A' && (s) <= 'D') || \ + (s) == '*' || (s) == '#' ) + +// 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 diff --git a/src/audio/tone_gen.cpp b/src/audio/tone_gen.cpp new file mode 100644 index 0000000..0f68c94 --- /dev/null +++ b/src/audio/tone_gen.cpp @@ -0,0 +1,245 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#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; + } +} + + diff --git a/src/audio/tone_gen.h b/src/audio/tone_gen.h new file mode 100644 index 0000000..1323fd4 --- /dev/null +++ b/src/audio/tone_gen.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _TONE_GEN_H +#define _TONE_GEN_H + +#include +#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 diff --git a/src/audio/twinkle_rtp_session.cpp b/src/audio/twinkle_rtp_session.cpp new file mode 100644 index 0000000..8d2da01 --- /dev/null +++ b/src/audio/twinkle_rtp_session.cpp @@ -0,0 +1,117 @@ + +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include "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; +} diff --git a/src/audio/twinkle_rtp_session.h b/src/audio/twinkle_rtp_session.h new file mode 100644 index 0000000..2bb9f62 --- /dev/null +++ b/src/audio/twinkle_rtp_session.h @@ -0,0 +1,53 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/audio/twinkle_zrtp_ui.cpp b/src/audio/twinkle_zrtp_ui.cpp new file mode 100644 index 0000000..d0709ca --- /dev/null +++ b/src/audio/twinkle_zrtp_ui.cpp @@ -0,0 +1,296 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 + diff --git a/src/audio/twinkle_zrtp_ui.h b/src/audio/twinkle_zrtp_ui.h new file mode 100644 index 0000000..66861dc --- /dev/null +++ b/src/audio/twinkle_zrtp_ui.h @@ -0,0 +1,90 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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_ + diff --git a/src/audits/Makefile.am b/src/audits/Makefile.am new file mode 100644 index 0000000..0c72765 --- /dev/null +++ b/src/audits/Makefile.am @@ -0,0 +1,9 @@ +AM_CPPFLAGS = \ + -Wall \ + -I$(top_srcdir)/src + +noinst_LIBRARIES = libaudits.a + +libaudits_a_SOURCES =\ + memman.cpp\ + memman.h diff --git a/src/audits/memman.cpp b/src/audits/memman.cpp new file mode 100644 index 0000000..0c87834 --- /dev/null +++ b/src/audits/memman.cpp @@ -0,0 +1,238 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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(); +} diff --git a/src/audits/memman.h b/src/audits/memman.h new file mode 100644 index 0000000..e3a25b9 --- /dev/null +++ b/src/audits/memman.h @@ -0,0 +1,87 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/auth.cpp b/src/auth.cpp new file mode 100644 index 0000000..ff5c4bc --- /dev/null +++ b/src/auth.cpp @@ -0,0 +1,231 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include "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 (c.auth_scheme != AUTH_DIGEST) { + 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; +} diff --git a/src/auth.h b/src/auth.h new file mode 100644 index 0000000..a4a755c --- /dev/null +++ b/src/auth.h @@ -0,0 +1,153 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 succesful 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 diff --git a/src/call_history.cpp b/src/call_history.cpp new file mode 100644 index 0000000..b0fe6f1 --- /dev/null +++ b/src/call_history.cpp @@ -0,0 +1,455 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include +#include "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() { + 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); + + 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); + + 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()); + + 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; + + 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) { + // Check number of fields + if (v.size() != 20) return false; + + time_start = strtoul(v[0].c_str(), NULL, 10); + time_answer = strtoul(v[1].c_str(), NULL, 10); + time_end = strtoul(v[2].c_str(), 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; +} + +//////////////////////// +// 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); +} diff --git a/src/call_history.h b/src/call_history.h new file mode 100644 index 0000000..b7fd3b6 --- /dev/null +++ b/src/call_history.h @@ -0,0 +1,227 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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(); + + /** + * 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; +}; + +/** 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 diff --git a/src/call_script.cpp b/src/call_script.cpp new file mode 100644 index 0000000..571efe1 --- /dev/null +++ b/src/call_script.cpp @@ -0,0 +1,459 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include "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 + } +} diff --git a/src/call_script.h b/src/call_script.h new file mode 100644 index 0000000..4253bda --- /dev/null +++ b/src/call_script.h @@ -0,0 +1,199 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 +#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 diff --git a/src/client_request.cpp b/src/client_request.cpp new file mode 100644 index 0000000..599f303 --- /dev/null +++ b/src/client_request.cpp @@ -0,0 +1,124 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/client_request.h b/src/client_request.h new file mode 100644 index 0000000..ca6ee95 --- /dev/null +++ b/src/client_request.h @@ -0,0 +1,127 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** @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 diff --git a/src/cmd_socket.cpp b/src/cmd_socket.cpp new file mode 100644 index 0000000..3f38999 --- /dev/null +++ b/src/cmd_socket.cpp @@ -0,0 +1,200 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include "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, ""); +} + +} diff --git a/src/cmd_socket.h b/src/cmd_socket.h new file mode 100644 index 0000000..b2af8f4 --- /dev/null +++ b/src/cmd_socket.h @@ -0,0 +1,66 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/dialog.cpp b/src/dialog.cpp new file mode 100644 index 0000000..f6e1790 --- /dev/null +++ b/src/dialog.cpp @@ -0,0 +1,3825 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include "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 (!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; +} diff --git a/src/dialog.h b/src/dialog.h new file mode 100644 index 0000000..7466d22 --- /dev/null +++ b/src/dialog.h @@ -0,0 +1,845 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 succesfully. + * @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 succesfully 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 diff --git a/src/diamondcard.cpp b/src/diamondcard.cpp new file mode 100644 index 0000000..b153ef1 --- /dev/null +++ b/src/diamondcard.cpp @@ -0,0 +1,126 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/diamondcard.h b/src/diamondcard.h new file mode 100644 index 0000000..bc6b924 --- /dev/null +++ b/src/diamondcard.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** @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 diff --git a/src/epa.cpp b/src/epa.cpp new file mode 100644 index 0000000..857100d --- /dev/null +++ b/src/epa.cpp @@ -0,0 +1,516 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; + } +} diff --git a/src/epa.h b/src/epa.h new file mode 100644 index 0000000..b457328 --- /dev/null +++ b/src/epa.h @@ -0,0 +1,216 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/events.cpp b/src/events.cpp new file mode 100644 index 0000000..45c4f1d --- /dev/null +++ b/src/events.cpp @@ -0,0 +1,726 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include "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"; + } + + 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(char _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_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_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(); +} diff --git a/src/events.h b/src/events.h new file mode 100644 index 0000000..c13e9c0 --- /dev/null +++ b/src/events.h @@ -0,0 +1,840 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 "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) */ +}; + +/** 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. */ + char 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(char _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; + //@} +}; + + +/** + * 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); + + /** + * 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 diff --git a/src/exceptions.h b/src/exceptions.h new file mode 100644 index 0000000..e6bb82a --- /dev/null +++ b/src/exceptions.h @@ -0,0 +1,38 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/gui/address_finder.cpp b/src/gui/address_finder.cpp new file mode 100644 index 0000000..1e692e2 --- /dev/null +++ b/src/gui/address_finder.cpp @@ -0,0 +1,123 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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); +} diff --git a/src/gui/address_finder.h b/src/gui/address_finder.h new file mode 100644 index 0000000..0872728 --- /dev/null +++ b/src/gui/address_finder.h @@ -0,0 +1,79 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/gui/addresscardform.ui b/src/gui/addresscardform.ui new file mode 100644 index 0000000..7532b11 --- /dev/null +++ b/src/gui/addresscardform.ui @@ -0,0 +1,234 @@ + +AddressCardForm + + + AddressCardForm + + + + 0 + 0 + 604 + 209 + + + + Twinkle - Address Card + + + + unnamed + + + + layout73 + + + + unnamed + + + + remarkTextLabel + + + &Remark: + + + remarkLineEdit + + + + + infixNameLineEdit + + + Infix name of contact. + + + + + firstNameLineEdit + + + First name of contact. + + + + + firstNameTextLabel + + + &First name: + + + firstNameLineEdit + + + + + remarkLineEdit + + + You may place any remark about the contact here. + + + + + phoneTextLabel + + + &Phone: + + + phoneLineEdit + + + + + infixNameTextLabel + + + &Infix name: + + + infixNameLineEdit + + + + + phoneLineEdit + + + Phone number or SIP address of contact. + + + + + lastNameLineEdit + + + Last name of contact. + + + + + lastNameTextLabel + + + &Last name: + + + lastNameLineEdit + + + + + + + spacer100 + + + Vertical + + + Expanding + + + + 20 + 31 + + + + + + layout72 + + + + unnamed + + + + spacer99 + + + Horizontal + + + Expanding + + + + 261 + 20 + + + + + + okPushButton + + + &OK + + + Alt+O + + + true + + + + + cancelPushButton + + + &Cancel + + + Alt+C + + + + + + + + + okPushButton + clicked() + AddressCardForm + validate() + + + cancelPushButton + clicked() + AddressCardForm + reject() + + + + firstNameLineEdit + infixNameLineEdit + lastNameLineEdit + phoneLineEdit + remarkLineEdit + okPushButton + cancelPushButton + + + address_book.h + gui.h + addresscardform.ui.h + + + validate() + + + exec( t_address_card & card ) + + + + diff --git a/src/gui/addresscardform.ui.h b/src/gui/addresscardform.ui.h new file mode 100644 index 0000000..f6ff3f6 --- /dev/null +++ b/src/gui/addresscardform.ui.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +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().stripWhiteSpace().ascii(); + card.name_infix = infixNameLineEdit->text().stripWhiteSpace().ascii(); + card.name_last = lastNameLineEdit->text().stripWhiteSpace().ascii(); + card.sip_address = phoneLineEdit->text().stripWhiteSpace().ascii(); + card.remark = remarkLineEdit->text().stripWhiteSpace().ascii(); + } + + return retval; +} + +void AddressCardForm::validate() +{ + QString firstName = firstNameLineEdit->text().stripWhiteSpace(); + QString infixName = infixNameLineEdit->text().stripWhiteSpace(); + QString lastName = lastNameLineEdit->text().stripWhiteSpace(); + QString phone = phoneLineEdit->text().stripWhiteSpace(); + + if (firstName.isEmpty() && infixName.isEmpty() && lastName.isEmpty()) { + ((t_gui *)ui)->cb_show_msg(this, + tr("You must fill in a name.").ascii(), 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.").ascii(), MSG_CRITICAL); + phoneLineEdit->setFocus(); + return; + } + + accept(); +} diff --git a/src/gui/addresslistviewitem.cpp b/src/gui/addresslistviewitem.cpp new file mode 100644 index 0000000..9c927f6 --- /dev/null +++ b/src/gui/addresslistviewitem.cpp @@ -0,0 +1,41 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "addresslistviewitem.h" + +// Columns +#define COL_ADDR_NAME 0 +#define COL_ADDR_PHONE 1 +#define COL_ADDR_REMARK 2 + +AddressListViewItem::AddressListViewItem(QListView *parent, const t_address_card &card) : + QListViewItem(parent, card.get_display_name().c_str(), + card.sip_address.c_str(), card.remark.c_str()), + address_card(card) +{} + +t_address_card AddressListViewItem::getAddressCard(void) const { + return address_card; +} + +void AddressListViewItem::update(const t_address_card &card) { + address_card = card; + setText(COL_ADDR_NAME, card.get_display_name().c_str()); + setText(COL_ADDR_PHONE, card.sip_address.c_str()); + setText(COL_ADDR_REMARK, card.remark.c_str()); +} diff --git a/src/gui/addresslistviewitem.h b/src/gui/addresslistviewitem.h new file mode 100644 index 0000000..7e1a960 --- /dev/null +++ b/src/gui/addresslistviewitem.h @@ -0,0 +1,36 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _ADDRESSLISTVIEWITEM_H +#define _ADDRESSLISTVIEWITEM_H + +#include "address_book.h" +#include "qlistview.h" + +class AddressListViewItem : public QListViewItem { +private: + t_address_card address_card; + +public: + AddressListViewItem(QListView *parent, const t_address_card &card); + t_address_card getAddressCard(void) const; + void update(const t_address_card &card); +}; + + +#endif diff --git a/src/gui/authenticationform.ui b/src/gui/authenticationform.ui new file mode 100644 index 0000000..acd98d1 --- /dev/null +++ b/src/gui/authenticationform.ui @@ -0,0 +1,340 @@ + +AuthenticationForm + + + AuthenticationForm + + + + 0 + 0 + 582 + 198 + + + + Twinkle - Authentication + + + + unnamed + + + + layout33 + + + + unnamed + + + + authIconTextLabel + + + + + + password.png + + + + + spacer46 + + + Vertical + + + Expanding + + + + 20 + 51 + + + + + + + + layout9 + + + + unnamed + + + + userValueTextLabel + + + + 0 + 85 + 255 + + + + user + No need to translate + + + The user for which authentication is requested. + + + + + profileValueTextLabel + + + + 7 + 0 + 0 + 0 + + + + + 0 + 85 + 255 + + + + profile + No need to translate + + + The user profile of the user for which authentication is requested. + + + + + profileTextLabel + + + User profile: + + + + + userTextLabel + + + User: + + + + + + + layout10 + + + + unnamed + + + + passwordTextLabel + + + &Password: + + + passwordLineEdit + + + + + passwordLineEdit + + + + 200 + 0 + + + + Password + + + Your password for authentication. + + + + + usernameLineEdit + + + + 200 + 0 + + + + Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. + + + + + usernameTextLabel + + + &User name: + + + usernameLineEdit + + + + + + + spacer13 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + layout22 + + + + unnamed + + + + spacer12 + + + Horizontal + + + Expanding + + + + 101 + 20 + + + + + + okPushButton + + + &OK + + + true + + + + + cancelPushButton + + + &Cancel + + + + + + + layout13 + + + + unnamed + + + + authTextLabel + + + + 32767 + 32767 + + + + Login required for realm: + + + + + realmTextLabel + + + + 7 + 0 + 0 + 0 + + + + + 0 + 85 + 255 + + + + realm + No need to translate + + + The realm for which you need to authenticate. + + + + + + + + + okPushButton + clicked() + AuthenticationForm + accept() + + + cancelPushButton + clicked() + AuthenticationForm + reject() + + + + usernameLineEdit + passwordLineEdit + okPushButton + cancelPushButton + + + user.h + authenticationform.ui.h + + + exec( t_user * user_config, const QString & realm, QString & username, QString & password ) + + + + diff --git a/src/gui/authenticationform.ui.h b/src/gui/authenticationform.ui.h new file mode 100644 index 0000000..a2e0905 --- /dev/null +++ b/src/gui/authenticationform.ui.h @@ -0,0 +1,44 @@ +/**************************************************************************** +** 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, preserving your code. Create 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +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; +} diff --git a/src/gui/buddyform.ui b/src/gui/buddyform.ui new file mode 100644 index 0000000..16369d2 --- /dev/null +++ b/src/gui/buddyform.ui @@ -0,0 +1,255 @@ + +BuddyForm + + + BuddyForm + + + + 0 + 0 + 484 + 154 + + + + Twinkle - Buddy + + + + unnamed + + + + layout12 + + + + unnamed + + + + spacer25 + + + Vertical + + + Expanding + + + + 20 + 47 + + + + + + addressToolButton + + + TabFocus + + + + + + kontact_contacts.png + + + Address book + + + Select an address from the address book. + + + + + phoneTextLabel + + + &Phone: + + + phoneLineEdit + + + + + nameLineEdit + + + Name of your buddy. + + + + + subscribeCheckBox + + + &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. + + + + + nameTextLabel + + + &Name: + + + nameLineEdit + + + + + phoneLineEdit + + + SIP address your buddy. + + + + + + + spacer14 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + layout54 + + + + unnamed + + + + spacer13 + + + Horizontal + + + Expanding + + + + 131 + 20 + + + + + + okPushButton + + + &OK + + + Alt+O + + + true + + + + + cancelPushButton + + + &Cancel + + + Alt+C + + + + + + + + + okPushButton + clicked() + BuddyForm + validate() + + + cancelPushButton + clicked() + BuddyForm + reject() + + + addressToolButton + clicked() + BuddyForm + showAddressBook() + + + + nameLineEdit + phoneLineEdit + subscribeCheckBox + addressToolButton + okPushButton + cancelPushButton + + + presence/buddy.h + user.h + qlistview.h + getaddressform.h + gui.h + sockets/url.h + buddylistview.h + audits/memman.h + buddyform.ui.h + + + GetAddressForm *getAddressForm; + t_user *user_config; + bool edit_mode; + t_buddy_list *buddy_list; + t_buddy *edit_buddy; + QListViewItem *profileItem; + + + showNew( t_buddy_list & _buddy_list, QListViewItem * _profileItem ) + showEdit( t_buddy & buddy ) + validate() + showAddressBook() + selectedAddress( const QString & name, const QString & phone ) + + + init() + destroy() + + + + diff --git a/src/gui/buddyform.ui.h b/src/gui/buddyform.ui.h new file mode 100644 index 0000000..d7f0714 --- /dev/null +++ b/src/gui/buddyform.ui.h @@ -0,0 +1,153 @@ +/**************************************************************************** +** 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +void BuddyForm::init() +{ + getAddressForm = 0; +} + +void BuddyForm::destroy() +{ + if (getAddressForm) { + MEMMAN_DELETE(getAddressForm); + delete getAddressForm; + } +} + +void BuddyForm::showNew(t_buddy_list &_buddy_list, QListViewItem *_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().stripWhiteSpace(); + QString address = phoneLineEdit->text().stripWhiteSpace(); + + if (name.isEmpty()) { + ((t_gui *)ui)->cb_show_msg(this, + tr("You must fill in a name.").ascii(), MSG_CRITICAL); + nameLineEdit->setFocus(); + return; + } + + string dest = ui->expand_destination(user_config, address.ascii()); + t_url dest_url(dest); + if (!dest_url.is_valid()) { + ((t_gui *)ui)->cb_show_msg(this, + tr("Invalid phone.").ascii(), 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().stripWhiteSpace().ascii()); + edit_buddy->set_sip_address(phoneLineEdit->text().stripWhiteSpace().ascii()); + 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().stripWhiteSpace().ascii()); + buddy.set_sip_address(phoneLineEdit->text().stripWhiteSpace().ascii()); + 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.ascii(), MSG_CRITICAL); + } + + accept(); +} + +void BuddyForm::showAddressBook() +{ + if (!getAddressForm) { + getAddressForm = new GetAddressForm( + this, "select address", 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); +} diff --git a/src/gui/buddylistview.cpp b/src/gui/buddylistview.cpp new file mode 100644 index 0000000..60f8ffc --- /dev/null +++ b/src/gui/buddylistview.cpp @@ -0,0 +1,276 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "buddylistview.h" + +#include "gui.h" + +#include "qapplication.h" +#include "qfont.h" +#include "qheader.h" +#include "qpixmap.h" +#include "qrect.h" +#include "qsize.h" +#include "qstylesheet.h" + +void AbstractBLVItem::set_icon(t_presence_state::t_basic_state state) { + switch (state) { + case t_presence_state::ST_BASIC_UNKNOWN: + setPixmap(0, QPixmap::fromMimeSource("presence_unknown.png")); + break; + case t_presence_state::ST_BASIC_CLOSED: + setPixmap(0, QPixmap::fromMimeSource("presence_offline.png")); + break; + case t_presence_state::ST_BASIC_OPEN: + setPixmap(0, QPixmap::fromMimeSource("presence_online.png")); + break; + case t_presence_state::ST_BASIC_FAILED: + setPixmap(0, QPixmap::fromMimeSource("presence_failed.png")); + break; + case t_presence_state::ST_BASIC_REJECTED: + setPixmap(0, QPixmap::fromMimeSource("presence_rejected.png")); + break; + default: + setPixmap(0, QPixmap::fromMimeSource("presence_unknown.png")); + break; + } +} + +AbstractBLVItem::AbstractBLVItem(QListViewItem *parent, const QString &text) : + QListViewItem(parent, text) +{} + +AbstractBLVItem::AbstractBLVItem(QListView *parent, const QString &text) : + QListViewItem(parent, 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()); + + tip = ""; + tip += QStyleSheet::escape(ui->format_sip_address(user_config, buddy->get_name(), t_url(url_str)).c_str()).replace(' ', " "); + + if (!buddy->get_may_subscribe_presence()) { + setPixmap(0, QPixmap::fromMimeSource("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 = buddy->get_presence_state()->get_failure_msg().c_str(); + 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(QListViewItem *parent, t_buddy *_buddy) : + AbstractBLVItem(parent, _buddy->get_name().c_str()), + buddy(_buddy) +{ + set_icon(); + buddy->attach(this); +} + +BuddyListViewItem::~BuddyListViewItem() { + buddy->detach(this); +} + +void BuddyListViewItem::update(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, buddy->get_name().c_str()); + QListViewItem::parent()->sort(); + } + ui->unlock(); +} + +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 = presence_epa->get_user_profile()->get_profile_name().c_str(); + + tip = ""; + tip += QStyleSheet::escape(profile_name); + 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"); + setPixmap(0, QPixmap::fromMimeSource("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); + } + setPixmap(0, QPixmap::fromMimeSource("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(QListView *parent, t_presence_epa *_presence_epa) : + AbstractBLVItem(parent, _presence_epa->get_user_profile()->get_profile_name().c_str()), + presence_epa(_presence_epa) +{ + set_icon(); + presence_epa->attach(this); +} + +BLViewUserItem::~BLViewUserItem() { + presence_epa->detach(this); +} + +void BLViewUserItem::paintCell(QPainter *painter, const QColorGroup &cg, + int column, int width, int align) +{ + painter->save(); + QFont font = painter->font(); + font.setBold(true); + painter->setFont(font); + QListViewItem::paintCell(painter, cg, column, width, align); + painter->restore(); +} + +void BLViewUserItem::update(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, presence_epa->get_user_profile()->get_profile_name().c_str()); + QListViewItem::listView()->sort(); + } + ui->unlock(); +} + +void BLViewUserItem::subject_destroyed(void) { + delete this; +} + +t_presence_epa *BLViewUserItem::get_presence_epa(void) { + return presence_epa; +} + + +BuddyListViewTip::BuddyListViewTip(QListView *parent) : + QToolTip(parent->viewport()), + parentListView(parent) +{} + +void BuddyListViewTip::maybeTip ( const QPoint & p ) { + QListView *listView = parentListView; + + QListViewItem *item = listView->itemAt(p); + if (!item) return; + + AbstractBLVItem *bitem = dynamic_cast(item); + if (!bitem) return; + + int x = listView->header()->sectionPos( listView->header()->mapToIndex( 0 ) ) + + listView->treeStepSize() * ( item->depth() + ( listView->rootIsDecorated() ? 1 : 0) ) + + listView->itemMargin(); + + if ( p.x() > x || + p.x() < listView->header()->sectionPos( listView->header()->mapToIndex( 0 ) ) ) + { + // p is not on root decoration + QRect tipRect = listView->itemRect(item); + + // Shrink rect to exclude root decoration + tipRect.setX(x); + tip(tipRect, bitem->get_tip()); + } +} diff --git a/src/gui/buddylistview.h b/src/gui/buddylistview.h new file mode 100644 index 0000000..1345901 --- /dev/null +++ b/src/gui/buddylistview.h @@ -0,0 +1,92 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef BUDDYLISTVIEW_H +#define BUDDYLISTVIEW_H + +#include "qlistview.h" +#include "qpainter.h" +#include "qtooltip.h" +#include "presence/buddy.h" +#include "presence/presence_epa.h" +#include "patterns/observer.h" + +class AbstractBLVItem : public QListViewItem { +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(QListViewItem *parent, const QString &text); + AbstractBLVItem(QListView *parent, const QString &text); + virtual ~AbstractBLVItem(); + virtual QString get_tip(void); +}; + +// List view item representing a buddy. +class BuddyListViewItem : public AbstractBLVItem, public patterns::t_observer { +private: + t_buddy *buddy; + + // Set the presence icon to reflect the buddy's presence + void set_icon(void); + +public: + BuddyListViewItem(QListViewItem *parent, t_buddy *_buddy); + virtual ~BuddyListViewItem(); + + virtual void update(void); + virtual void subject_destroyed(void); + + t_buddy *get_buddy(void); +}; + +// List view item representing a user +class BLViewUserItem : public AbstractBLVItem, public patterns::t_observer { +private: + t_presence_epa *presence_epa; + + void set_icon(void); + +public: + BLViewUserItem(QListView *parent, t_presence_epa *_presence_epa); + virtual ~BLViewUserItem(); + + void paintCell(QPainter *painter, const QColorGroup &cg, + int column, int width, int align); + + virtual void update(void); + virtual void subject_destroyed(void); + + t_presence_epa *get_presence_epa(void); +}; + +class BuddyListViewTip : public QToolTip { +private: + QListView *parentListView; + +public: + BuddyListViewTip(QListView *parent); + virtual ~BuddyListViewTip() {}; + void maybeTip ( const QPoint & p ); +}; + +#endif diff --git a/src/gui/command_args.h b/src/gui/command_args.h new file mode 100644 index 0000000..d792b4d --- /dev/null +++ b/src/gui/command_args.h @@ -0,0 +1,61 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/gui/core_strings.h b/src/gui/core_strings.h new file mode 100644 index 0000000..baebbaa --- /dev/null +++ b/src/gui/core_strings.h @@ -0,0 +1,108 @@ +// 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 .")) + diff --git a/src/gui/deregisterform.ui b/src/gui/deregisterform.ui new file mode 100644 index 0000000..fe70e5b --- /dev/null +++ b/src/gui/deregisterform.ui @@ -0,0 +1,105 @@ + +DeregisterForm + + + DeregisterForm + + + + 0 + 0 + 287 + 82 + + + + + 0 + 0 + 0 + 0 + + + + Twinkle - Deregister + + + + unnamed + + + + deregAllCheckBox + + + deregister all devices + + + + + layout21 + + + + unnamed + + + + spacer13 + + + Horizontal + + + Expanding + + + + 111 + 20 + + + + + + okPushButton + + + &OK + + + true + + + + + cancelPushButton + + + &Cancel + + + + + + + + + okPushButton + clicked() + DeregisterForm + accept() + + + cancelPushButton + clicked() + DeregisterForm + reject() + + + + deregisterform.ui.h + + + + diff --git a/src/gui/deregisterform.ui.h b/src/gui/deregisterform.ui.h new file mode 100644 index 0000000..2aae11c --- /dev/null +++ b/src/gui/deregisterform.ui.h @@ -0,0 +1,26 @@ +/**************************************************************************** +** 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, preserving your code. Create 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + diff --git a/src/gui/diamondcardprofileform.ui b/src/gui/diamondcardprofileform.ui new file mode 100644 index 0000000..d74ae86 --- /dev/null +++ b/src/gui/diamondcardprofileform.ui @@ -0,0 +1,307 @@ + +DiamondcardProfileForm + + + DiamondcardProfileForm + + + + 0 + 0 + 541 + 433 + + + + 0 + + + Twinkle - Diamondcard User Profile + + + + unnamed + + + + explainTextLabel + + + + 5 + 5 + 0 + 0 + + + + <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> + + + RichText + + + + + spacer35 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + layout193 + + + + unnamed + + + + accountIdLineEdit + + + Your Diamondcard account ID. + + + + + nameLineEdit + + + 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. + + + + + accountIdTextLabel + + + &Account ID: + + + accountIdLineEdit + + + + + pinCodeTextLabel + + + &PIN code: + + + pinCodeLineEdit + + + + + nameTextLabel + + + &Your name: + + + nameLineEdit + + + + + pinCodeLineEdit + + + Password + + + Your Diamondcard PIN code. + + + + + + + spacer27 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + layout63 + + + + unnamed + + + + signUpTextLabel + + + PaletteBackground + + + + 0 + 0 + 205 + + + + 13 + + + <p align="center"><u>Sign up for a Diamondcard account</u></p> + + + AlignVCenter + + + + + + + spacer28 + + + Vertical + + + Expanding + + + + 20 + 21 + + + + + + layout64 + + + + unnamed + + + + spacer21 + + + Horizontal + + + Expanding + + + + 61 + 20 + + + + + + okPushButton + + + &OK + + + Alt+O + + + true + + + + + cancelPushButton + + + &Cancel + + + Alt+C + + + + + + + + + okPushButton + clicked() + DiamondcardProfileForm + validate() + + + cancelPushButton + clicked() + DiamondcardProfileForm + reject() + + + + nameLineEdit + accountIdLineEdit + pinCodeLineEdit + okPushButton + cancelPushButton + + + user.h + qregexp.h + qvalidator.h + gui.h + diamondcard.h + getprofilenameform.h + audits/memman.h + diamondcardprofileform.ui.h + + + t_user *user_config; + bool destroy_user_config; + + + success() + newDiamondcardProfile(const QString&) + + + destroyOldUserConfig() + show( t_user * user ) + validate() + mouseReleaseEvent( QMouseEvent * e ) + processLeftMouseButtonRelease( QMouseEvent * e ) + + + init() + destroy() + exec( t_user * user ) + + + + diff --git a/src/gui/diamondcardprofileform.ui.h b/src/gui/diamondcardprofileform.ui.h new file mode 100644 index 0000000..eedec55 --- /dev/null +++ b/src/gui/diamondcardprofileform.ui.h @@ -0,0 +1,151 @@ +/**************************************************************************** +** 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +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.").ascii(), + MSG_CRITICAL); + accountIdLineEdit->setFocus(); + return; + } + + if (pinCodeLineEdit->text().isEmpty()) { + ((t_gui *)ui)->cb_show_msg(this, tr("Fill in your PIN code.").ascii(), + 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.ascii())) { + ((t_gui *)ui)->cb_show_msg(this, + tr("A user profile with name %1 already exists.").arg(profileName).ascii(), + MSG_WARNING); + + // Ask user for a profile name + GetProfileNameForm getProfileNameForm(this, "get profile name", true); + if (!getProfileNameForm.execNewName()) return; + + profileName = getProfileNameForm.getProfileName(); + filename = profileName; + filename.append(USER_FILE_EXT); + } + + diamondcard_set_user_config(*user_config, + nameLineEdit->text().ascii(), + accountIdLineEdit->text().ascii(), + pinCodeLineEdit->text().ascii()); + + 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->hasMouse()) { + string url = diamondcard_url(DC_ACT_SIGNUP, "", ""); + ((t_gui *)ui)->open_url_in_browser(url.c_str()); + } else { + e->ignore(); + } +} diff --git a/src/gui/dtmfform.ui b/src/gui/dtmfform.ui new file mode 100644 index 0000000..d0e683d --- /dev/null +++ b/src/gui/dtmfform.ui @@ -0,0 +1,509 @@ + +DtmfForm + + + DtmfForm + + + + 0 + 0 + 350 + 302 + + + + + 5 + 5 + 0 + 0 + + + + Twinkle - DTMF + + + + unnamed + + + + keypadGroupBox + + + Keypad + + + + unnamed + + + + twoPushButton + + + + 10 + + + + + + + dtmf-2.png + + + 2 + + + + + threePushButton + + + + 10 + + + + + + + dtmf-3.png + + + 3 + + + + + aPushButton + + + + 194 + 202 + 210 + + + + + + + dtmf-a.png + + + Over decadic A. Normally not needed. + + + + + fourPushButton + + + + + + dtmf-4.png + + + 4 + + + + + fivePushButton + + + + + + dtmf-5.png + + + 5 + + + + + sixPushButton + + + + + + dtmf-6.png + + + 6 + + + + + bPushButton + + + + 194 + 202 + 210 + + + + + + + dtmf-b.png + + + Over decadic B. Normally not needed. + + + + + sevenPushButton + + + + + + dtmf-7.png + + + 7 + + + + + eightPushButton + + + + + + dtmf-8.png + + + 8 + + + + + ninePushButton + + + + + + dtmf-9.png + + + 9 + + + + + cPushButton + + + + 194 + 202 + 210 + + + + + + + dtmf-c.png + + + Over decadic C. Normally not needed. + + + + + starPushButton + + + + + + dtmf-star.png + + + Star (*) + + + + + zeroPushButton + + + + + + dtmf-0.png + + + 0 + + + + + poundPushButton + + + + + + dtmf-pound.png + + + Pound (#) + + + + + dPushButton + + + + 194 + 202 + 210 + + + + + + + dtmf-d.png + + + Over decadic D. Normally not needed. + + + + + onePushButton + + + + 10 + + + + + + + dtmf-1.png + + + true + + + 1 + + + + + + + layout24 + + + + unnamed + + + + spacer20 + + + Horizontal + + + Expanding + + + + 291 + 20 + + + + + + closePushButton + + + &Close + + + Alt+C + + + true + + + + + + + + + closePushButton + clicked() + DtmfForm + accept() + + + onePushButton + clicked() + DtmfForm + dtmf1() + + + twoPushButton + clicked() + DtmfForm + dtmf2() + + + threePushButton + clicked() + DtmfForm + dtmf3() + + + fourPushButton + clicked() + DtmfForm + dtmf4() + + + fivePushButton + clicked() + DtmfForm + dtmf5() + + + sixPushButton + clicked() + DtmfForm + dtmf6() + + + sevenPushButton + clicked() + DtmfForm + dtmf7() + + + eightPushButton + clicked() + DtmfForm + dtmf8() + + + ninePushButton + clicked() + DtmfForm + dtmf9() + + + zeroPushButton + clicked() + DtmfForm + dtmf0() + + + starPushButton + clicked() + DtmfForm + dtmfStar() + + + poundPushButton + clicked() + DtmfForm + dtmfPound() + + + aPushButton + clicked() + DtmfForm + dtmfA() + + + bPushButton + clicked() + DtmfForm + dtmfB() + + + cPushButton + clicked() + DtmfForm + dtmfC() + + + dPushButton + clicked() + DtmfForm + dtmfD() + + + + onePushButton + twoPushButton + threePushButton + aPushButton + fourPushButton + fivePushButton + sixPushButton + bPushButton + sevenPushButton + eightPushButton + ninePushButton + cPushButton + starPushButton + zeroPushButton + poundPushButton + dPushButton + closePushButton + + + qregexp.h + qvalidator.h + qlineedit.h + dtmfform.ui.h + + + digits(const QString &) + + + dtmf1() + dtmf2() + dtmf3() + dtmf4() + dtmf5() + dtmf6() + dtmf7() + dtmf8() + dtmf9() + dtmf0() + dtmfStar() + dtmfPound() + dtmfA() + dtmfB() + dtmfC() + dtmfD() + keyPressEvent( QKeyEvent * e ) + + + + diff --git a/src/gui/dtmfform.ui.h b/src/gui/dtmfform.ui.h new file mode 100644 index 0000000..5e155a8 --- /dev/null +++ b/src/gui/dtmfform.ui.h @@ -0,0 +1,177 @@ +/**************************************************************************** +** 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, preserving your code. Create 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +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 + switch (e->key()) { + case Qt::Key_1: + dtmf1(); + break; + case Qt::Key_2: + case Qt::Key_A: + case Qt::Key_B: + case Qt::Key_C: + dtmf2(); + break; + case Qt::Key_3: + case Qt::Key_D: + case Qt::Key_E: + case Qt::Key_F: + dtmf3(); + break; + case Qt::Key_4: + case Qt::Key_G: + case Qt::Key_H: + case Qt::Key_I: + dtmf4(); + break; + case Qt::Key_5: + case Qt::Key_J: + case Qt::Key_K: + case Qt::Key_L: + dtmf5(); + break; + case Qt::Key_6: + case Qt::Key_M: + case Qt::Key_N: + case Qt::Key_O: + dtmf6(); + break; + case Qt::Key_7: + case Qt::Key_P: + case Qt::Key_Q: + case Qt::Key_R: + case Qt::Key_S: + dtmf7(); + break; + case Qt::Key_8: + case Qt::Key_T: + case Qt::Key_U: + case Qt::Key_V: + dtmf8(); + break; + case Qt::Key_9: + case Qt::Key_W: + case Qt::Key_X: + case Qt::Key_Y: + case Qt::Key_Z: + dtmf9(); + break; + case Qt::Key_0: + case Qt::Key_Space: + dtmf0(); + break; + case Qt::Key_Asterisk: + dtmfStar(); + break; + case Qt::Key_NumberSign: + dtmfPound(); + break; + default: + e->ignore(); + } +} diff --git a/src/gui/freedesksystray.cpp b/src/gui/freedesksystray.cpp new file mode 100644 index 0000000..caab5c6 --- /dev/null +++ b/src/gui/freedesksystray.cpp @@ -0,0 +1,165 @@ +/*************************************************************************** + * Copyright (C) 2004 by Emil Stoyanov * + * emosto@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +// 2006 Modified by Michel de Boer + +#include "freedesksystray.h" + +FreeDeskSysTray::FreeDeskSysTray ( QWidget *pParent , const char *pszName ) + : QLabel(pParent, pszName, WMouseNoMask | WRepaintNoErase | WType_TopLevel | WStyle_Customize | WStyle_NoBorder | WStyle_StaysOnTop) +{ + mainWindow = pParent; + trayMenu = new QPopupMenu(this); +} + +void FreeDeskSysTray::dock () +{ + trayMenu->insertSeparator(); + trayMenu->insertItem(tr("Show/Hide"), this, SLOT(slotMenuItemShow())) ; + + QIconSet quitIcon(QPixmap::fromMimeSource("exit.png")); + trayMenu->insertItem(quitIcon, tr("Quit"), this, SLOT(slotMenuItemQuit())) ; + + Display *dpy = QPaintDevice::x11AppDisplay(); + WId trayWin = winId(); + + // System Tray Protocol Specification from freedesktop.org + Screen *screen = XDefaultScreenOfDisplay(dpy); + int iScreen = XScreenNumberOfScreen(screen); + char szAtom[32]; + snprintf(szAtom, sizeof(szAtom), "_NET_SYSTEM_TRAY_S%d", iScreen); + Atom selectionAtom = XInternAtom(dpy, szAtom, false); + XGrabServer(dpy); + Window managerWin = XGetSelectionOwner(dpy, selectionAtom); + XSelectInput(dpy, managerWin, StructureNotifyMask); + XUngrabServer(dpy); + XFlush(dpy); + XEvent ev; + memset(&ev, 0, sizeof(ev)); + ev.xclient.type = ClientMessage; + ev.xclient.window = managerWin; + ev.xclient.message_type = XInternAtom(dpy, "_NET_SYSTEM_TRAY_OPCODE", true); + ev.xclient.format = 32; + ev.xclient.data.l[0] = CurrentTime; + ev.xclient.data.l[1] = SYSTEM_TRAY_REQUEST_DOCK; + ev.xclient.data.l[2] = trayWin; + ev.xclient.data.l[3] = 0; + ev.xclient.data.l[4] = 0; + XSendEvent(dpy, managerWin, false, NoEventMask, &ev); + XSync(dpy, false); + + Atom trayAtom; + // KDE 3 + WId forWin = mainWindow ? mainWindow->topLevelWidget()->winId() : qt_xrootwin(); + trayAtom = XInternAtom(dpy, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR", false); + XChangeProperty(dpy, trayWin, trayAtom, XA_WINDOW, 32, PropModeReplace, (unsigned char *) &forWin, 1); + + setMinimumSize(22, 22); + setBackgroundMode(Qt::X11ParentRelative); + + // because of GNOME - needs a wait of at least 50-100 ms, otherwise width=1 + // KDocker solves the problem so (bug?) + QTimer::singleShot(500, this, SLOT(show())); + +} + +void FreeDeskSysTray::undock () +{ + XUnmapWindow(QPaintDevice::x11AppDisplay(), winId()); + hide(); +} + +FreeDeskSysTray::~FreeDeskSysTray () +{} + +void FreeDeskSysTray::mousePressEvent ( QMouseEvent *pMouseEvent ) +{ + if (!QLabel::rect().contains(pMouseEvent->pos())) + return; + + switch (pMouseEvent->button()) + { + + case LeftButton: + slotMenuItemShow(); + break; + + case RightButton: + showContextMenu(pMouseEvent->globalPos()); + break; + + default: + break; + } +} + +void FreeDeskSysTray::setPixmapOverlay ( const QPixmap& pmOverlay ) +{ + QWidget *pParent = parentWidget(); + if (pParent == 0) + return; + + // Get base pixmap from parent widget. + QPixmap pm; + pm.convertFromImage(pParent->icon()->convertToImage().smoothScale(22, 22), 0); + + // Merge with the overlay pixmap. + QBitmap bmMask(*pm.mask()); + bitBlt(&bmMask, 0, 0, pmOverlay.mask(), 0, 0, -1, -1, Qt::OrROP); + pm.setMask(bmMask); + bitBlt(&pm, 0, 0, &pmOverlay); + + QLabel::setPixmap(pm); +} + +QPopupMenu *FreeDeskSysTray::contextMenu() +{ + return trayMenu; +} + + void FreeDeskSysTray::setPixmap(const QPixmap& pixmap) +{ + QLabel::setPixmap(pixmap); + repaint(true); +} + +void FreeDeskSysTray::showContextMenu(const QPoint& position) +{ + trayMenu->popup(position,0); +} + +void FreeDeskSysTray::slotMenuItemShow() { + +// mainWindowGeometry = mainWindow->geometry(); +// windowPos = mainWindow->frameGeometry().topLeft(); + + if (mainWindow->isVisible()) { + //mainWindow->setGeometry(mainWindowGeometry); + mainWindow->close(); + } else { +// mainWindow->move( windowPos ); // restore position + mainWindow->show(); + } + +} + +void FreeDeskSysTray::slotMenuItemQuit() { + emit quitSelected(); +} diff --git a/src/gui/freedesksystray.h b/src/gui/freedesksystray.h new file mode 100644 index 0000000..cd41a1f --- /dev/null +++ b/src/gui/freedesksystray.h @@ -0,0 +1,94 @@ +/*************************************************************************** + * Copyright (C) 2004 by Emil Stoyanov * + * emosto@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +// 2006 Modified by Michel de Boer + +#ifndef FREEDESKSYSTEMTRAY_H +#define FREEDESKTSYSTEMTRAY_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + + +// System Tray Protocol Specification opcodes. +#define SYSTEM_TRAY_REQUEST_DOCK 0 +#define SYSTEM_TRAY_BEGIN_MESSAGE 1 +#define SYSTEM_TRAY_CANCEL_MESSAGE 2 + + +class FreeDeskSysTray : public QLabel +{ + Q_OBJECT + +public: + + FreeDeskSysTray(QWidget *pParent = 0, const char *pszName = 0); + ~FreeDeskSysTray(); + + void setPixmapOverlay(const QPixmap& pmOverlay); + void showContextMenu(const QPoint& position); + void dock (); + void undock (); + QPopupMenu *contextMenu(); + void setPixmap(const QPixmap& pixmap); + +public: + + QWidget * mainWindow; + QPopupMenu * trayMenu; + +protected: + + void mousePressEvent(QMouseEvent *); + +public slots: + + void slotMenuItemShow(); + void slotMenuItemQuit(); + +signals: + + void quitSelected(); + +private: + QRect mainWindowGeometry; + QPoint windowPos; +}; + +// ifndef FREEDESKSYSTEMTRAY_H +#endif diff --git a/src/gui/getaddressform.ui b/src/gui/getaddressform.ui new file mode 100644 index 0000000..153fdd1 --- /dev/null +++ b/src/gui/getaddressform.ui @@ -0,0 +1,476 @@ + +GetAddressForm + + + GetAddressForm + + + + 0 + 0 + 655 + 474 + + + + Twinkle - Select address + + + + unnamed + + + + addressTabWidget + + + Rounded + + + + tabKABC + + + &KAddressBook + + + + unnamed + + + + + Name + + + true + + + true + + + + + Type + + + true + + + true + + + + + Phone + + + true + + + true + + + + addressListView + + + Manual + + + true + + + true + + + 1 + + + LastColumn + + + 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. + + + + + layout17 + + + + unnamed + + + + sipOnlyCheckBox + + + &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>". + + + + + spacer16 + + + Horizontal + + + Expanding + + + + 201 + 20 + + + + + + + + layout69 + + + + unnamed + + + + reloadPushButton + + + &Reload + + + Alt+R + + + Reload the list of addresses from KAddressbook. + + + + + spacer59 + + + Horizontal + + + Expanding + + + + 491 + 20 + + + + + + + + + + tabLocal + + + &Local address book + + + + unnamed + + + + + Name + + + true + + + true + + + + + Phone + + + true + + + true + + + + + Remark + + + true + + + true + + + + localListView + + + true + + + true + + + LastColumn + + + Contacts in the local address book of Twinkle. + + + + + layout67 + + + + unnamed + + + + addPushButton + + + &Add + + + Alt+A + + + Add a new contact to the local address book. + + + + + deletePushButton + + + &Delete + + + Alt+D + + + Delete a contact from the local address book. + + + + + editPushButton + + + &Edit + + + Alt+E + + + Edit a contact from the local address book. + + + + + spacer97 + + + Horizontal + + + Expanding + + + + 161 + 20 + + + + + + + + + + + layout68 + + + + unnamed + + + + spacer5 + + + Horizontal + + + Expanding + + + + 378 + 20 + + + + + + okPushButton + + + &OK + + + Alt+O + + + true + + + + + cancelPushButton + + + &Cancel + + + Alt+C + + + + + + + + + okPushButton + clicked() + GetAddressForm + selectAddress() + + + cancelPushButton + clicked() + GetAddressForm + reject() + + + addressListView + doubleClicked(QListViewItem*) + GetAddressForm + selectKABCAddress() + + + sipOnlyCheckBox + toggled(bool) + GetAddressForm + toggleSipOnly(bool) + + + reloadPushButton + clicked() + GetAddressForm + reload() + + + localListView + doubleClicked(QListViewItem*) + GetAddressForm + selectLocalAddress() + + + addPushButton + clicked() + GetAddressForm + addLocalAddress() + + + deletePushButton + clicked() + GetAddressForm + deleteLocalAddress() + + + editPushButton + clicked() + GetAddressForm + editLocalAddress() + + + + addressListView + sipOnlyCheckBox + reloadPushButton + addressTabWidget + localListView + addPushButton + deletePushButton + editPushButton + okPushButton + cancelPushButton + + + user.h + qlistview.h + qstring.h + qmessagebox.h + protocol.h + sys_settings.h + util.h + qregexp.h + sockets/url.h + gui.h + address_book.h + addresslistviewitem.h + addresscardform.h + getaddressform.ui.h + + + void *addrBook; + + + address(const QString &, const QString &) + address(const QString &) + + + reload() + show() + loadAddresses() + loadLocalAddresses() + selectAddress() + selectKABCAddress() + selectLocalAddress() + toggleSipOnly( bool on ) + addLocalAddress() + deleteLocalAddress() + editLocalAddress() + + + init() + + + + diff --git a/src/gui/getaddressform.ui.h b/src/gui/getaddressform.ui.h new file mode 100644 index 0000000..b1f221d --- /dev/null +++ b/src/gui/getaddressform.ui.h @@ -0,0 +1,259 @@ +/**************************************************************************** +** 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 + +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(tabKABC, false); + addressTabWidget->setCurrentPage(TAB_LOCAL); +#endif + loadLocalAddresses(); +} + +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 QListViewItem(addressListView, i->realName(), + (*j).typeLabel(), phone); + } + } + } + + QListViewItem *first = addressListView->firstChild(); + if (first) addressListView->setSelected(first, true); +#endif +} + +void GetAddressForm::loadLocalAddresses() +{ + localListView->clear(); + const list &address_list = ab_local->get_address_list(); + + for(list::const_iterator i = address_list.begin(); i != address_list.end(); i++) + { + new AddressListViewItem(localListView, *i); + } + + QListViewItem *first = localListView->firstChild(); + if (first) localListView->setSelected(first, true); +} + +void GetAddressForm::selectAddress() +{ + if (addressTabWidget->currentPageIndex() == TAB_KABC) { + selectKABCAddress(); + } else { + selectLocalAddress(); + } +} + +void GetAddressForm::selectKABCAddress() +{ +#ifdef HAVE_KDE + QListViewItem *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() +{ + AddressListViewItem *item = dynamic_cast( + localListView->selectedItem()); + if (item) { + t_address_card card = item->getAddressCard(); + emit(card.get_display_name().c_str(), card.sip_address.c_str()); + + // 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; + if (f.exec(card)) { + ab_local->add_address(card); + new AddressListViewItem(localListView, card); + + string error_msg; + if (!ab_local->save(error_msg)) { + ui->cb_show_msg(error_msg, MSG_CRITICAL); + } + } +} + +void GetAddressForm::deleteLocalAddress() +{ + AddressListViewItem *item = dynamic_cast( + localListView->selectedItem()); + if (item) { + t_address_card card = item->getAddressCard(); + if (ab_local->del_address(card)) { + delete item; + + string error_msg; + if (!ab_local->save(error_msg)) { + ui->cb_show_msg(error_msg, MSG_CRITICAL); + } + } + } +} + +void GetAddressForm::editLocalAddress() +{ + AddressListViewItem *item = dynamic_cast( + localListView->selectedItem()); + if (!item) return; + + t_address_card oldCard = item->getAddressCard(); + t_address_card newCard = oldCard; + AddressCardForm f; + if (f.exec(newCard)) { + if (ab_local->update_address(oldCard, newCard)) { + item->update(newCard); + + string error_msg; + if (!ab_local->save(error_msg)) { + ui->cb_show_msg(error_msg, MSG_CRITICAL); + } + } + } +} diff --git a/src/gui/getprofilenameform.ui b/src/gui/getprofilenameform.ui new file mode 100644 index 0000000..599d778 --- /dev/null +++ b/src/gui/getprofilenameform.ui @@ -0,0 +1,174 @@ + +GetProfileNameForm + + + GetProfileNameForm + + + + 0 + 0 + 430 + 127 + + + + Twinkle - Profile name + + + + unnamed + + + + userIconTextLabel + + + WidgetOrigin + + + + + + penguin_big.png + + + + + layout24 + + + + unnamed + + + + spacer41 + + + Horizontal + + + Expanding + + + + 81 + 20 + + + + + + okPushButton + + + &OK + + + true + + + + + cancelPushButton + + + &Cancel + + + + + + + layout26 + + + + unnamed + + + + profileTextLabel + + + + 5 + 0 + 0 + 0 + + + + Enter a name for your profile: + + + false + + + AlignVCenter + + + + + profileLineEdit + + + + + + <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> + + + + + + + + + cancelPushButton + clicked() + GetProfileNameForm + reject() + + + okPushButton + clicked() + GetProfileNameForm + validate() + + + + profileLineEdit + okPushButton + cancelPushButton + + + qlineedit.h + qregexp.h + qvalidator.h + protocol.h + qmessagebox.h + qdir.h + qfile.h + user.h + getprofilenameform.ui.h + + + validate() + + + init() + getProfileName() + execNewName() + execRename( const QString & oldName ) + + + + diff --git a/src/gui/getprofilenameform.ui.h b/src/gui/getprofilenameform.ui.h new file mode 100644 index 0000000..00c2f18 --- /dev/null +++ b/src/gui/getprofilenameform.ui.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** 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, preserving your code. Create 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +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(); +} diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp new file mode 100644 index 0000000..f7d836d --- /dev/null +++ b/src/gui/gui.cpp @@ -0,0 +1,3057 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "twinkle_config.h" + +#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 "twinklesystray.h" +#include "util.h" +#include "address_finder.h" +#include "yesnodialog.h" +#include "command_args.h" +#include "im/msg_session.h" + +#include "qcombobox.h" +#include "qhbox.h" +#include "qlabel.h" +#include "qlayout.h" +#include "qlistbox.h" +#include "qmessagebox.h" +#include "qpixmap.h" +#include "qprocess.h" +#include "qpushbutton.h" +#include "qsize.h" +#include "qsizepolicy.h" +#include "qstring.h" +#include "qtextcodec.h" +#include "qtextedit.h" +#include "qtoolbar.h" +#include "qtooltip.h" +#include "qvbox.h" + +extern string user_host; +extern pthread_t thread_id_main; + +// External command arguments +extern t_command_args g_cmd_args; + +QString str2html(const QString &s) +{ + QString result(s); + + result.replace('&', "&"); + result.replace('<', "<"); + result.replace('>', ">"); + + return result; +} + +void setDisabledIcon(QAction *action, const QString &icon) { + QIconSet i = action->iconSet(); + i.setPixmap(QPixmap::fromMimeSource(icon), + QIconSet::Automatic, QIconSet::Disabled); + action->setIconSet(i); +} + +void setDisabledIcon(QToolButton *toolButton, const QString &icon) { + QIconSet i = toolButton->iconSet(); + i.setPixmap(QPixmap::fromMimeSource(icon), + QIconSet::Automatic, QIconSet::Disabled); + toolButton->setIconSet(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(); + QToolTip::remove(fromLabel); + toLabel->clear(); + QToolTip::remove(toLabel); + subjectLabel->clear(); + QToolTip::remove(subjectLabel); + codecLabel->clear(); + photoLabel->clear(); + photoLabel->hide(); +} + +void t_gui::displayTo(const QString &s) { + toLabel->setText(s); + toLabel->setCursorPosition(0); + QToolTip::add(toLabel, s); +} + +void t_gui::displayFrom(const QString &s) { + fromLabel->setText(s); + fromLabel->setCursorPosition(0); + QToolTip::add(fromLabel, s); +} + +void t_gui::displaySubject(const QString &s) { + subjectLabel->setText(s); + subjectLabel->setCursorPosition(0); + QToolTip::add(subjectLabel, 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.smoothScale( + photoLabel->width(), photoLabel->height(), QImage::ScaleMin)); + photoLabel->setPixmap(pm); + photoLabel->show(); + } +} + +///////////////////////////////////////////////// +// PROTECTED +///////////////////////////////////////////////// +bool t_gui::do_invite(const string &destination, const string &display, + const string &subject, bool immediate, bool anonymous) +{ + lock(); + if (mainWindow->callInvite->isEnabled()) { + if (immediate) { + t_user *user = phone->ref_user_profile( + mainWindow->userComboBox->currentText().ascii()); + t_url dst_url(expand_destination(user, destination)); + if (dst_url.is_valid()) { + mainWindow->do_phoneInvite(user, + display.c_str(), dst_url, subject.c_str(), + anonymous); + } + } else { + t_url dest_url(destination); + t_display_url du(dest_url, display); + mainWindow->phoneInvite(du.encode().c_str(), subject.c_str(), + anonymous); + } + } + unlock(); + + return true; +} + +void t_gui::do_redial(void) { + lock(); + if (mainWindow->callRedial->isEnabled()) { + mainWindow->phoneRedial(); + } + unlock(); +} + +void t_gui::do_answer(void) { + lock(); + if (mainWindow->callAnswer->isEnabled()) { + mainWindow->phoneAnswer(); + } + unlock(); +} + +void t_gui::do_answerbye(void) { + lock(); + if (mainWindow->callAnswer->isEnabled()) { + mainWindow->phoneAnswer(); + } else if (mainWindow->callBye->isEnabled()) { + mainWindow->phoneBye(); + } + unlock(); +} + +void t_gui::do_reject(void) { + lock(); + if (mainWindow->callReject->isEnabled()) { + mainWindow->phoneReject(); + } + unlock(); +} + +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; + } + + t_user *user = phone->ref_user_profile(mainWindow-> + userComboBox->currentText().ascii()); + + 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) { + lock(); + if (enable) { + phone->ref_service(user)->enable_cf(cf_type, dest_list); + } else { + phone->ref_service(user)->disable_cf(cf_type); + } + mainWindow->updateServicesStatus(); + unlock(); + + return; + } else { + if (action_present) { + if (!enable) { + lock(); + 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(); + unlock(); + } + + return; + } + } + + lock(); + if (mainWindow->callRedirect->isEnabled()) { + if (immediate) { + mainWindow->do_phoneRedirect(dest_list); + } else { + mainWindow->phoneRedirect(dest_strlist); + } + } + unlock(); + + return; +} + +void t_gui::do_dnd(bool show_status, bool toggle, bool enable) { + if (show_status) { + // Show status not supported in GUI + return; + } + + lock(); + if (phone->ref_users().size() == 1) { + if (toggle) { + enable = !mainWindow->serviceDnd->isOn(); + } + mainWindow->srvDnd(enable); + mainWindow->serviceDnd->setOn(enable); + } else { + t_user *user = phone->ref_user_profile(mainWindow-> + userComboBox->currentText().ascii()); + 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); + } + } + unlock(); +} + +void t_gui::do_auto_answer(bool show_status, bool toggle, bool enable) { + if (show_status) { + // Show status not supported in GUI + return; + } + + lock(); + if (phone->ref_users().size() == 1) { + if (toggle) { + enable = !mainWindow->serviceAutoAnswer->isOn(); + } + mainWindow->srvAutoAnswer(enable); + mainWindow->serviceAutoAnswer->setOn(enable); + } else { + t_user *user = phone->ref_user_profile(mainWindow-> + userComboBox->currentText().ascii()); + 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); + } + } + unlock(); +} + +void t_gui::do_bye(void) { + lock(); + if (mainWindow->callBye->isEnabled()) { + mainWindow->phoneBye(); + } + unlock(); +} + +void t_gui::do_hold(void) { + lock(); + if (mainWindow->callHold->isEnabled() && !mainWindow->callHold->isOn()) { + mainWindow->phoneHold(true); + } + unlock(); +} + +void t_gui::do_retrieve(void) { + lock(); + if (mainWindow->callHold->isEnabled() && mainWindow->callHold->isOn()) { + mainWindow->phoneHold(false); + } + unlock(); +} + +bool t_gui::do_refer(const string &destination, t_transfer_type transfer_type, bool immediate) { + lock(); + if (mainWindow->callTransfer->isEnabled() && + !mainWindow->callTransfer->isOn()) + { + if (immediate) { + t_display_url du; + t_user *user = phone->ref_user_profile(mainWindow-> + userComboBox->currentText().ascii()); + du.url = expand_destination(user, destination); + + if (du.is_valid() || transfer_type == TRANSFER_OTHER_LINE) { + mainWindow->do_phoneTransfer(du, transfer_type); + } + } else { + mainWindow->phoneTransfer(destination, transfer_type); + } + } else if(mainWindow->callTransfer->isEnabled() && + mainWindow->callTransfer->isOn()) + { + if (transfer_type != TRANSFER_CONSULT) { + mainWindow->do_phoneTransferLine(); + } + } + unlock(); + + return true; +} + +void t_gui::do_conference(void) { + lock(); + if (mainWindow->callConference->isEnabled()) { + mainWindow->phoneConference(); + } + unlock(); +} + +void t_gui::do_mute(bool show_status, bool toggle, bool enable) { + if (show_status) { + // Show status not supported in GUI + return; + } + + lock(); + if (mainWindow->callMute->isEnabled()) { + if (toggle) enable = !phone->is_line_muted(phone->get_active_line()); + mainWindow->phoneMute(enable); + } + unlock(); +} + +void t_gui::do_dtmf(const string &digits) { + lock(); + if (mainWindow->callDTMF->isEnabled()) { + mainWindow->sendDTMF(digits.c_str()); + } + unlock(); +} + +void t_gui::do_register(bool reg_all_profiles) { + lock(); + list l; + + if (reg_all_profiles) { + l = phone->ref_users(); + } else { + t_user *user = phone->ref_user_profile(mainWindow-> + userComboBox->currentText().ascii()); + l.push_back(user); + } + + mainWindow->do_phoneRegister(l); + unlock(); +} + +void t_gui::do_deregister(bool dereg_all_profiles, bool dereg_all_devices) { + lock(); + list l; + + if (dereg_all_profiles) { + l = phone->ref_users(); + } else { + t_user *user = phone->ref_user_profile(mainWindow-> + userComboBox->currentText().ascii()); + l.push_back(user); + } + + if (dereg_all_devices) { + mainWindow->do_phoneDeregisterAll(l); + } else { + mainWindow->do_phoneDeregister(l); + } + unlock(); +} + +void t_gui::do_fetch_registrations(void) { + lock(); + mainWindow->phoneShowRegistrations(); + unlock(); +} + +bool t_gui::do_options(bool dest_set, const string &destination, bool immediate) { + lock(); + // 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) { + t_user *user = phone->ref_user_profile(mainWindow-> + userComboBox->currentText().ascii()); + t_url dst_url(expand_destination(user, destination)); + + if (dst_url.is_valid()) { + mainWindow->do_phoneTermCap(user, dst_url); + } + } else { + mainWindow->phoneTermCap(destination.c_str()); + } + unlock(); + + 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) { + lock(); + for (int i = 0; i < mainWindow->userComboBox->count(); i++) { + if (mainWindow->userComboBox->text(i) == profile_name.c_str()) + { + mainWindow->userComboBox->setCurrentItem(i); + } + } + unlock(); +} + +bool t_gui::do_message(const string &destination, const string &display, + const im::t_msg &msg) +{ + t_user *user = phone->ref_user_profile(mainWindow-> + userComboBox->currentText().ascii()); + + 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) { + t_user *user = phone->ref_user_profile(mainWindow-> + userComboBox->currentText().ascii()); + + phone->pub_publish_presence(user, basic_state); +} + +void t_gui::do_zrtp(t_zrtp_cmd zrtp_cmd) { + lock(); + + switch (zrtp_cmd) { + case ZRTP_ENCRYPT: + mainWindow->phoneEnableZrtp(true); + break; + case ZRTP_GO_CLEAR: + mainWindow->phoneEnableZrtp(false); + break; + case ZRTP_CONFIRM_SAS: + mainWindow->phoneConfirmZrtpSas(); + break; + case ZRTP_RESET_SAS: + mainWindow->phoneResetZrtpSasConfirmation(); + break; + default: + assert(false); + } + + unlock(); +} + + + +void t_gui::do_quit(void) { + lock(); + mainWindow->fileExit(); + unlock(); +} + +void t_gui::do_help(const list &al) { + // Nothing to do in GUI mode + return; +} + + +///////////////////////////////////////////////// +// PUBLIC +///////////////////////////////////////////////// + +t_gui::t_gui(t_phone *_phone) : t_userintf(_phone), timerUpdateMessageSessions(NULL) +{ + use_stdout = false; + lastFileBrowsePath = DIR_HOME; + + mainWindow = new MphoneForm(0, 0, Qt::WType_TopLevel | Qt::WStyle_ContextHelp); +#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); + qApp->setMainWidget(mainWindow); +} + +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->setCaption(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-2009 "; + s.append(PRODUCT_AUTHOR); + mainWindow->display(s); + + // Restore user interface state from previous session + restore_state(); + + // Initialize phone functions + 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().ascii()) + { + // 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.ascii(), 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.ascii(), + 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.ascii(), + 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().ascii()); + + list history; + for (int i = 0; i < mainWindow->callComboBox->count(); i++) { + history.push_back(mainWindow->callComboBox->text(i).ascii()); + } + 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(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.").ascii(), + 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, "nic", 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()); + sf->nicListBox->insertItem( + QPixmap::fromMimeSource("kcmpci16.png"), + item); + } + + sf->nicListBox->setCurrentItem(0); + sf->exec(); + + int selection = sf->nicListBox->currentItem(); + 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(0, "select user profile", 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 + mainWindow->displayHeader(); + 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()); + mainWindow->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); + mainWindow->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; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: far end cancelled call.").arg(line + 1); + mainWindow->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; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: far end released call.").arg(line + 1); + mainWindow->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; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: SDP answer from far end not supported.").arg(line + 1); + mainWindow->display(s); + + s = reason.c_str(); + mainWindow->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; + + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: SDP answer from far end missing.").arg(line + 1); + mainWindow->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; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: Unsupported content type in answer from far end.").arg(line + 1); + mainWindow->display(s); + + s = r->hdr_content_type.media.type.c_str(); + s.append("/").append(r->hdr_content_type.media.subtype.c_str()); + mainWindow->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; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: no ACK received, call will be terminated.").arg(line + 1); + mainWindow->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; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: no PRACK received, call will be terminated.").arg(line + 1); + mainWindow->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; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: PRACK failed.").arg(line + 1); + mainWindow->display(s); + + s = QString().setNum(r->code); + s.append(' ').append(r->reason.c_str()); + mainWindow->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(); + mainWindow->updateState(); + unlock(); +} + +void t_gui::cb_cancel_failed(int line, const t_response *r) { + if (line >= NUM_USER_LINES) return; + + lock(); + QString s; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: failed to cancel call.").arg(line + 1); + mainWindow->display(s); + + s = QString().setNum(r->code); + s.append(' ').append(r->reason.c_str()); + mainWindow->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); + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: far end answered call.").arg(line + 1); + mainWindow->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; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: call failed.").arg(line + 1); + mainWindow->display(s); + + s = QString().setNum(r->code); + s.append(' ').append(r->reason.c_str()); + mainWindow->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++) { + mainWindow->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(); + mainWindow->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(); + mainWindow->display(s); + } + } + + // Unsupported extensions + if (r->code == R_420_BAD_EXTENSION) { + mainWindow->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; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: call failed.").arg(line + 1); + mainWindow->display(s); + + unlock(); +} + +void t_gui::cb_call_ended(int line) { + if (line >= NUM_USER_LINES) return; + + lock(); + QString s; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: call released.").arg(line + 1); + mainWindow->display(s); + + unlock(); +} + +void t_gui::cb_call_established(int line) { + if (line >= NUM_USER_LINES) return; + + lock(); + QString s; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: call established.").arg(line + 1); + mainWindow->display(s); + + unlock(); +} + +void t_gui::cb_options_response(const t_response *r) { + lock(); + QString s; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Response on terminal capability request: %1 %2") + .arg(r->code).arg(r->reason.c_str()); + mainWindow->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()); + mainWindow->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")); + } + mainWindow->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")); + } + mainWindow->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")); + } + mainWindow->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")); + } + mainWindow->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")); + } + mainWindow->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")); + } + mainWindow->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; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: call retrieve failed.").arg(line + 1); + mainWindow->display(s); + + s = QString().setNum(r->code); + s.append(' ').append(r->reason.c_str()); + mainWindow->display(s); + + unlock(); +} + + +void t_gui::cb_invalid_reg_resp(t_user *user_config, const t_response *r, const string &reason) { + lock(); + QString s; + + mainWindow->displayHeader(); + qApp->translate("GUI", "%1, registration failed: %2 %3") + .arg(user_config->get_profile_name().c_str()) + .arg(r->code) + .arg(r->reason.c_str()); + mainWindow->display(s); + mainWindow->display(reason.c_str()); + + mainWindow->updateRegStatus(); + 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) { + mainWindow->displayHeader(); + s = qApp->translate("GUI", "%1, registration succeeded (expires = %2 seconds)") + .arg(user_config->get_profile_name().c_str()) + .arg(expires); + mainWindow->display(s); + } + + mainWindow->updateRegStatus(); + unlock(); +} + +void t_gui::cb_register_failed(t_user *user_config, const t_response *r, bool first_failure) { + lock(); + QString s; + + if (first_failure) { + mainWindow->displayHeader(); + 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()); + mainWindow->display(s); + } + + mainWindow->updateRegStatus(); + unlock(); +} + +void t_gui::cb_register_stun_failed(t_user *user_config, bool first_failure) { + lock(); + QString s; + + if (first_failure) { + mainWindow->displayHeader(); + s = qApp->translate("GUI", "%1, registration failed: STUN failure") + .arg(user_config->get_profile_name().c_str()); + mainWindow->display(s); + } + + mainWindow->updateRegStatus(); + unlock(); +} + +void t_gui::cb_deregister_success(t_user *user_config, const t_response *r) { + lock(); + QString s; + + mainWindow->displayHeader(); + 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()); + mainWindow->display(s); + + mainWindow->updateRegStatus(); + unlock(); +} + +void t_gui::cb_deregister_failed(t_user *user_config, const t_response *r) { + lock(); + QString s; + + mainWindow->displayHeader(); + 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()); + mainWindow->display(s); + + mainWindow->updateRegStatus(); + unlock(); +} + +void t_gui::cb_fetch_reg_failed(t_user *user_config, const t_response *r) { + lock(); + QString s; + + mainWindow->displayHeader(); + 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()); + mainWindow->display(s); + + unlock(); +} + +void t_gui::cb_fetch_reg_result(t_user *user_config, const t_response *r) { + lock(); + QString s; + + mainWindow->displayHeader(); + + 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"); + mainWindow->display(s); + } else { + s += qApp->translate("GUI", ": you have the following registrations"); + mainWindow->display(s); + for (list::const_iterator i = l.begin(); + i != l.end(); i++) + { + mainWindow->display(i->encode().c_str()); + } + } + + unlock(); +} + +void t_gui::cb_register_inprog(t_user *user_config, t_register_type register_type) { + QString s; + + lock(); + + switch(register_type) { + case REG_REGISTER: + // Do not report registration refreshments + if (phone->get_is_registered(user_config)) break; + mainWindow->statRegLabel->setPixmap( + QPixmap::fromMimeSource("gear.png")); + break; + case REG_DEREGISTER: + case REG_DEREGISTER_ALL: + mainWindow->statRegLabel->setPixmap( + QPixmap::fromMimeSource("gear.png")); + break; + case REG_QUERY: + mainWindow->displayHeader(); + s = user_config->get_profile_name().c_str(); + s += qApp->translate("GUI", ": fetching registrations..."); + mainWindow->display(s); + break; + } + + unlock(); +} + +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; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: redirecting request to").arg(line + 1); + mainWindow->display(s); + + s = format_sip_address(user_config, contact.display, contact.uri).c_str(); + mainWindow->display(s); + + unlock(); +} + +void t_gui::cb_redirecting_request(t_user *user_config, const t_contact_param &contact) { + lock(); + QString s; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Redirecting request to: %1").arg( + format_sip_address(user_config, contact.display, contact.uri).c_str()); + mainWindow->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.ascii(), 50).c_str(); + } + QString presOrganization(""); + if (!organization.isEmpty()) { + presOrganization = dotted_truncate(organization.ascii(), 50).c_str(); + } + QString presSubject(""); + if (!subject.isEmpty()) { + presSubject = dotted_truncate(subject.ascii(), 50).c_str(); + } + QString presReferredByParty(""); + if (!referred_by_party.isEmpty()) { + presReferredByParty = qApp->translate("GUI", "Transferred by: %1").arg( + dotted_truncate(referred_by_party.ascii(), 40).c_str()); + } + + // Create photo pixmap. If no photo is available, then use + // the Twinkle icon. + QPixmap pm; + QFrame::Shape photoFrameShape = QFrame::NoFrame; + if (photo.isNull()) { + pm = QPixmap::fromMimeSource("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); + QVBox *popup_view = new QVBox(sys_tray_popup); + QHBox *hb = new QHBox(popup_view); + hb->setSpacing(5); + QLabel *lblPhoto = new QLabel(hb); + lblPhoto->setPixmap(pm); + lblPhoto->setFrameShape(photoFrameShape); + QVBox *vb = new QVBox(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 + + QHBox *buttonBox = new QHBox(vb); + QIconSet iconAnswer(QPixmap::fromMimeSource("answer.png")); + QPushButton *pbAnswer = new QPushButton(iconAnswer, + qApp->translate("SysTrayPopup", "Answer"), buttonBox); + QObject::connect(pbAnswer, SIGNAL(clicked()), + mainWindow, SLOT(phoneAnswerFromSystrayPopup())); + QIconSet iconReject(QPixmap::fromMimeSource("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].start( + sys_config->get_gui_auto_show_timeout() * 1000, true); + } + + 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, char dtmf_event) { + if (line >= NUM_USER_LINES) return; + + lock(); + QString s; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: DTMF detected:").arg(line + 1).append(" "); + + if (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)); + } + + mainWindow->display(s); + + unlock(); +} + +void t_gui::cb_send_dtmf(int line, char dtmf_event) { + if (line >= NUM_USER_LINES) return; + + lock(); + QString s; + + if (!VALID_DTMF_EV(dtmf_event)) return; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: send DTMF %2").arg(line + 1).arg( + dtmf_ev2char(dtmf_event)); + mainWindow->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(); + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: far end does not support DTMF telephone events.").arg(line + 1); + mainWindow->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(); + mainWindow->updateState(); + unlock(); +} + +void t_gui::cb_line_state_changed(void) { + lock(); + mainWindow->updateState(); + 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; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: received notification.").arg(line+1); + mainWindow->display(s); + + s = qApp->translate("GUI", "Event: %1").arg(r->hdr_event.event_type.c_str()); + mainWindow->display(s); + + s = qApp->translate("GUI", "State: %1").arg(r->hdr_subscription_state.substate.c_str()); + mainWindow->display(s); + + if (r->hdr_subscription_state.substate == SUBSTATE_TERMINATED) { + s = qApp->translate("GUI", "Reason: %1").arg(r->hdr_subscription_state.reason.c_str()); + mainWindow->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()); + mainWindow->display(s); + + unlock(); +} + +void t_gui::cb_refer_failed(int line, const t_response *r) { + if (line >= NUM_USER_LINES) return; + + lock(); + QString s; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: call transfer failed.").arg(line + 1); + mainWindow->display(s); + + s = QString().setNum(r->code); + s.append(' ').append(r->reason.c_str()); + mainWindow->display(s); + + // The refer state has changed, so update the main window. + mainWindow->updateState(); + + unlock(); +} + +void t_gui::cb_refer_result_success(int line) { + if (line >= NUM_USER_LINES) return; + + lock(); + QString s; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: call succesfully transferred.").arg(line + 1); + mainWindow->display(s); + + // The refer state has changed, so update the main window. + mainWindow->updateState(); + + unlock(); +} + +void t_gui::cb_refer_result_failed(int line) { + if (line >= NUM_USER_LINES) return; + + lock(); + QString s; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: call transfer failed.").arg(line + 1); + mainWindow->display(s); + + // The refer state has changed, so update the main window. + mainWindow->updateState(); + + unlock(); +} + +void t_gui::cb_refer_result_inprog(int line) { + if (line >= NUM_USER_LINES) return; + + lock(); + QString s; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: call transfer still in progress.").arg(line + 1); + mainWindow->display(s); + + s = qApp->translate("GUI", "No further notifications will be received."); + mainWindow->display(s); + + // The refer state has changed, so update the main window. + mainWindow->updateState(); + + 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(); + + mainWindow->displayHeader(); + 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()); + mainWindow->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()); + mainWindow->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(); + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: Call transfer failed. Retrieving original call.").arg(line + 1); + mainWindow->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; + + mainWindow->displayHeader(); + 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()); + mainWindow->display(s); + + unlock(); +} + +void t_gui::cb_stun_failed(t_user *user_config) { + lock(); + QString s; + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "%1, STUN request failed.") + .arg(user_config->get_profile_name().c_str()); + mainWindow->display(s); + + unlock(); +} + +bool t_gui::cb_ask_user_to_redirect_invite(t_user *user_config, const t_url &destination, + const string &display) +{ + QString s; + QString title; + + lock(); + + 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; + + unlock(); + + return permission; +} + +bool t_gui::cb_ask_user_to_redirect_request(t_user *user_config, + const t_url &destination, + const string &display, t_method method) +{ + QString s; + QString title; + + lock(); + + 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; + + unlock(); + + return permission; +} + +bool t_gui::cb_ask_credentials(t_user *user_config, const string &realm, string &username, + string &password) +{ + QString user(username.c_str()); + QString passwd(password.c_str()); + + lock(); + + AuthenticationForm *af = new AuthenticationForm(mainWindow, "authentication", + true); + MEMMAN_NEW(af); + if (!af->exec(user_config, QString(realm.c_str()), user, passwd)) { + MEMMAN_DELETE(af); + delete af; + unlock(); + return false; + } + + username = user.ascii(); + password = passwd.ascii(); + MEMMAN_DELETE(af); + delete af; + + unlock(); + return true; +} + +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) +{ + QString s; + QString title; + + lock(); + + 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(); + + unlock(); +} + +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()); + } + mainWindow->displayHeader(); + mainWindow->display(s); + + unlock(); +} + +void t_gui::cb_log_updated(bool log_zapped) { + lock(); + mainWindow->updateLog(log_zapped); + unlock(); +} + +void t_gui::cb_call_history_updated(void) { + lock(); + mainWindow->updateCallHistory(); + unlock(); +} + +void t_gui::cb_missed_call(int num_missed_calls) { + lock(); + mainWindow->updateMissedCallStatus(num_missed_calls); + unlock(); +} + +void t_gui::cb_nat_discovery_progress_start(int num_steps) { + natDiscoveryProgressDialog = new QProgressDialog( + qApp->translate("GUI", "Firewall / NAT discovery..."), + qApp->translate("GUI", "Abort"), + num_steps, NULL, + "nat discovery progress", true); + MEMMAN_NEW(natDiscoveryProgressDialog); + natDiscoveryProgressDialog->setCaption(PRODUCT_NAME); + natDiscoveryProgressDialog->setMinimumDuration(200); +} + +void t_gui::cb_nat_discovery_progress_step(int step) { + natDiscoveryProgressDialog->setProgress(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->wasCancelled(); +} + +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); + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1").arg(line + 1); + s.append(": SAS = ").append(sas.c_str()); + mainWindow->display(s); + s = qApp->translate("GUI", "Click the padlock to confirm a correct SAS."); + mainWindow->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.ascii(), MSG_WARNING); + } else { + cb_display_msg(msg.ascii(), MSG_WARNING); + } + + action_zrtp_go_clear_ok(line); +} + +void t_gui::cb_zrtp_sas_confirmed(int line) { + lock(); + QString s; + + setLineFields(line); + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: SAS confirmed.").arg(line + 1); + mainWindow->display(s); + + unlock(); +} + +void t_gui::cb_zrtp_sas_confirmation_reset(int line) { + lock(); + QString s; + + setLineFields(line); + + mainWindow->displayHeader(); + s = qApp->translate("GUI", "Line %1: SAS confirmation reset.").arg(line + 1); + mainWindow->display(s); + + unlock(); +} + +void t_gui::cb_update_mwi(void) { + lock(); + mainWindow->updateMwi(); + unlock(); +} + +void t_gui::cb_mwi_subscribe_failed(t_user *user_config, t_response *r, bool first_failure) { + lock(); + QString s; + + if (first_failure) { + mainWindow->displayHeader(); + s = qApp->translate("GUI", "%1, voice mail status failure.") + .arg(user_config->get_profile_name().c_str()); + mainWindow->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."); + } + + mainWindow->displayHeader(); + mainWindow->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()); + } 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"); + s.arg(attachment_error_msg.c_str()); + session->set_error(s.ascii()); + } + + 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").ascii(); + } 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) { + string subject; + string dst_no_headers; + t_display_url du; + + t_user *user = phone->ref_user_profile( + mainWindow->userComboBox->currentText().ascii()); + expand_destination(user, destination, du, subject, dst_no_headers); + if (!du.is_valid()) return; + + lock(); + 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); + } + unlock(); +} + +void t_gui::cmd_quit(void) { + lock(); + mainWindow->fileExit(); + unlock(); +} + +void t_gui::cmd_show(void) { + lock(); + if (mainWindow->isMinimized()) { + mainWindow->setWindowState(mainWindow->windowState() & ~Qt::WindowMinimized | Qt::WindowActive); + mainWindow->raise(); + } else { + mainWindow->show(); + mainWindow->raise(); + mainWindow->setActiveWindow(); + } + unlock(); +} + +void t_gui::cmd_hide(void) { + lock(); + if (sys_config->get_gui_use_systray()) { + mainWindow->hide(); + } else { + mainWindow->setWindowState(mainWindow->windowState() | Qt::WindowMinimized); + } + unlock(); +} + +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) { + for (list::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) { + for (list::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) { + for (list::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()); + + 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) { + 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 (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->insertItem((*i)->get_profile_name().c_str()); + } + + cb->setCurrentItem(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().ascii(); + } 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 + QProcess process; + bool process_started = false; + + QStringList browsers; + + if (sys_browser.empty()) { + browsers << "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.setArguments(QStringList() << *it << url); + process_started = process.start(); + 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.ascii(), MSG_CRITICAL); + } +} diff --git a/src/gui/gui.h b/src/gui/gui.h new file mode 100644 index 0000000..90c4f84 --- /dev/null +++ b/src/gui/gui.h @@ -0,0 +1,396 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 "qprogressdialog.h" +#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); + +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, char dtmf_event); + void cb_send_dtmf(int line, char 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); + +private slots: + /** + * Update timers associated with message sessions. This + * function should be called every second. + */ + void updateTimersMessageSessions(); +}; + +#endif diff --git a/src/gui/historyform.ui b/src/gui/historyform.ui new file mode 100644 index 0000000..aedf488 --- /dev/null +++ b/src/gui/historyform.ui @@ -0,0 +1,475 @@ + +HistoryForm + + + HistoryForm + + + + 0 + 0 + 872 + 647 + + + + Twinkle - Call History + + + + unnamed + + + + + Time + + + true + + + true + + + + + In/Out + + + true + + + true + + + + + From/To + + + true + + + true + + + + + Subject + + + true + + + true + + + + + Status + + + true + + + true + + + + historyListView + + + true + + + true + + + LastColumn + + + + + layout37 + + + + unnamed + + + + cdrGroupBox + + + Call details + + + + unnamed + + + + cdrTextEdit + + + RichText + + + NoWrap + + + true + + + AutoAll + + + Details of the selected call record. + + + + + + + viewGroupBox + + + View + + + + unnamed + + + + inCheckBox + + + &Incoming calls + + + Alt+I + + + Check this option to show incoming calls. + + + + + outCheckBox + + + &Outgoing calls + + + Alt+O + + + Check this option to show outgoing calls. + + + + + successCheckBox + + + &Answered calls + + + Alt+A + + + Check this option to show answered calls. + + + + + missedCheckBox + + + &Missed calls + + + Alt+M + + + Check this option to show missed calls. + + + + + profileCheckBox + + + Current &user profiles only + + + Alt+U + + + Check this option to show only calls associated with this user profile. + + + + + + + + + layout39 + + + + unnamed + + + + clearPushButton + + + 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> + + + + + spacer28 + + + Horizontal + + + Expanding + + + + 540 + 20 + + + + + + closePushButton + + + Clo&se + + + Alt+S + + + false + + + Close this window. + + + + + callPushButton + + + &Call + + + Alt+C + + + true + + + Call selected address. + + + + + + + layout6 + + + + unnamed + + + + numberlCallsTtextLabel + + + Number of calls: + + + + + numberCallsValueTextLabel + + + ### + + + + + totalDurationTextLabel + + + Total call duration: + + + + + totalDurationValueTextLabel + + + ### + + + + + spacer11 + + + Horizontal + + + Expanding + + + + 460 + 20 + + + + + + + + + + closePushButton + clicked() + HistoryForm + close() + + + historyListView + currentChanged(QListViewItem*) + HistoryForm + showCallDetails(QListViewItem*) + + + inCheckBox + toggled(bool) + HistoryForm + loadHistory() + + + missedCheckBox + toggled(bool) + HistoryForm + loadHistory() + + + outCheckBox + toggled(bool) + HistoryForm + loadHistory() + + + profileCheckBox + toggled(bool) + HistoryForm + loadHistory() + + + successCheckBox + toggled(bool) + HistoryForm + loadHistory() + + + historyListView + rightButtonPressed(QListViewItem*,const QPoint&,int) + HistoryForm + popupMenu(QListViewItem*,const QPoint&) + + + historyListView + doubleClicked(QListViewItem*,const QPoint&,int) + HistoryForm + call(QListViewItem*) + + + clearPushButton + clicked() + HistoryForm + clearHistory() + + + callPushButton + clicked() + HistoryForm + call() + + + + historyListView + cdrTextEdit + inCheckBox + outCheckBox + successCheckBox + missedCheckBox + profileCheckBox + clearPushButton + closePushButton + + + user.h + phone.h + qpopupmenu.h + call_history.h + util.h + gui.h + qlistview.h + historylistview.h + qiconset.h + audits/memman.h + historyform.ui.h + + + extern t_phone *phone; + + + time_t timeLastViewed; + QPopupMenu *histPopupMenu; + int itemCall; + + + call(t_user *, const QString &, const QString &, bool) + + + loadHistory() + update() + show() + closeEvent( QCloseEvent * e ) + showCallDetails( QListViewItem * item ) + popupMenu( QListViewItem * item, const QPoint & pos ) + call( QListViewItem * item ) + call( void ) + deleteEntry( void ) + clearHistory() + + + init() + destroy() + + + + diff --git a/src/gui/historyform.ui.h b/src/gui/historyform.ui.h new file mode 100644 index 0000000..6c47540 --- /dev/null +++ b/src/gui/historyform.ui.h @@ -0,0 +1,358 @@ +/**************************************************************************** +** 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +void HistoryForm::init() +{ + historyListView->setSorting(HISTCOL_TIMESTAMP, false); + historyListView->setColumnWidthMode(HISTCOL_FROMTO, QListView::Manual); + historyListView->setColumnWidth(HISTCOL_FROMTO, 200); + historyListView->setColumnWidthMode(HISTCOL_SUBJECT, QListView::Manual); + historyListView->setColumnWidth(HISTCOL_SUBJECT, 200); + + inCheckBox->setChecked(true); + outCheckBox->setChecked(true); + successCheckBox->setChecked(true); + missedCheckBox->setChecked(true); + profileCheckBox->setChecked(true); + + timeLastViewed = phone->get_startup_time(); + + QIconSet inviteIcon(QPixmap::fromMimeSource("invite.png")); + QIconSet deleteIcon(QPixmap::fromMimeSource("editdelete.png")); + histPopupMenu = new QPopupMenu(this); + MEMMAN_NEW(histPopupMenu); + + itemCall = histPopupMenu->insertItem(inviteIcon, tr("Call..."), this, SLOT(call())); + histPopupMenu->insertItem(deleteIcon, tr("Delete"), this, SLOT(deleteEntry())); +} + +void HistoryForm::destroy() +{ + MEMMAN_DELETE(histPopupMenu); + delete histPopupMenu; +} + +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; + historyListView->clear(); + list history; + call_history->get_history(history); + for (list::iterator i = history.begin(); i != history.end(); i++) { + if (i->direction == t_call_record::DIR_IN && !inCheckBox->isChecked()) { + continue; + } + if (i->direction == t_call_record::DIR_OUT && !outCheckBox->isChecked()) { + continue; + } + if (i->invite_resp_code < 300 && !successCheckBox->isChecked()) { + continue; + } + if (i->invite_resp_code >= 300 && !missedCheckBox->isChecked()) { + continue; + } + if (!profile_name_list.contains(i->user_profile.c_str()) && + profileCheckBox->isChecked()) + { + continue; + } + + numberOfCalls++; + + // Calculate total duration + totalCallDuration += i->time_end - i->time_start; + if (i->time_answer != 0) { + totalConversationDuration += i->time_end - i->time_answer; + } + + t_user *user_config = phone->ref_user_profile(i->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(); + } + + new HistoryListViewItem(historyListView, + *i, user_config, timeLastViewed); + } + + 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); + + // Make the first entry the selected entry. + QListViewItem *first = historyListView->firstChild(); + if (first) { + historyListView->setSelected(first, true); + showCallDetails(first); + } else { + cdrTextEdit->clear(); + } +} + +// 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 (isShown()) loadHistory(); +} + +void HistoryForm::show() +{ + if (isShown()) { + raise(); + setActiveWindow(); + 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(QListViewItem *item) +{ + QString s; + + t_call_record cr = ((HistoryListViewItem *)item)->get_call_record(); + cdrTextEdit->clear(); + + 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(); + } + + 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(QListViewItem *item, const QPoint &pos) +{ + if (!item) return; + + HistoryListViewItem *histItem = dynamic_cast(item); + if (!histItem) return; + + t_call_record cr = histItem->get_call_record(); + + // An anonymous caller cannot be called + bool canCall = !(cr.direction == t_call_record::DIR_IN && + cr.from_uri.encode() == ANONYMOUS_URI); + + histPopupMenu->setItemEnabled(itemCall, canCall); + histPopupMenu->popup(pos); +} + +void HistoryForm::call(QListViewItem *item) +{ + if (!item) return; + + HistoryListViewItem *histItem = (HistoryListViewItem *)item; + t_call_record cr = histItem->get_call_record(); + + 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:").ascii()) { + 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, item->text(HISTCOL_FROMTO), subject, hide_user); + } +} + +void HistoryForm::call(void) +{ + QListViewItem *item = historyListView->currentItem(); + if (item) call(item); +} + +void HistoryForm::deleteEntry(void) +{ + QListViewItem *item = historyListView->currentItem(); + HistoryListViewItem *histItem = dynamic_cast(item); + if (!histItem) return; + + call_history->delete_call_record(histItem->get_call_record().get_id()); +} + +void HistoryForm::clearHistory() +{ + call_history->clear(); +} diff --git a/src/gui/historylistview.cpp b/src/gui/historylistview.cpp new file mode 100644 index 0000000..2f9806c --- /dev/null +++ b/src/gui/historylistview.cpp @@ -0,0 +1,89 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "historylistview.h" +#include "util.h" +#include "userintf.h" + +#include "qpixmap.h" + +HistoryListViewItem::HistoryListViewItem( QListView * parent, const t_call_record &cr, t_user *user_config, time_t _last_viewed) : + QListViewItem(parent, + time2str(cr.time_start, "%d %b %Y %H:%M:%S").c_str(), + cr.get_direction().c_str(), + (cr.direction == t_call_record::DIR_IN ? + ui->format_sip_address(user_config, + cr.from_display, cr.from_uri).c_str() : + ui->format_sip_address(user_config, + cr.to_display, cr.to_uri).c_str()), + cr.subject.c_str(), + cr.invite_resp_reason.c_str()) +{ + call_record = cr; + last_viewed = _last_viewed; + + // Set direction icon + setPixmap(HISTCOL_DIRECTION, (cr.direction == t_call_record::DIR_IN ? + QPixmap::fromMimeSource("1leftarrow-yellow.png") : + QPixmap::fromMimeSource("1rightarrow.png"))); + + // Set status icon + setPixmap(HISTCOL_STATUS, (cr.invite_resp_code < 300 ? + QPixmap::fromMimeSource("ok.png") : + QPixmap::fromMimeSource("cancel.png"))); +} + +void HistoryListViewItem::paintCell(QPainter *painter, const QColorGroup &cg, + int column, int width, int align) +{ + painter->save(); + QColorGroup grp(cg); + if (call_record.time_start > last_viewed && + call_record.rel_cause == t_call_record::CS_FAILURE && + call_record.direction == t_call_record::DIR_IN) + { + // Highlight missed calls since last view + grp.setColor(QColorGroup::Base, QColor("yellow")); + } + QListViewItem::paintCell(painter, grp, column, width, align); + painter->restore(); +} + +int HistoryListViewItem::compare ( QListViewItem * i, int col, bool ascending ) const +{ + if (col != HISTCOL_TIMESTAMP) { + return QListViewItem::compare(i, col, ascending); + } + if (call_record.time_start < ((HistoryListViewItem *)i)->get_time_start()) { + return -1; + } + if (call_record.time_start == ((HistoryListViewItem *)i)->get_time_start()) { + return 0; + } + return 1; +} + +time_t HistoryListViewItem::get_time_start(void) const +{ + return call_record.time_start; +} + +t_call_record HistoryListViewItem::get_call_record(void) const +{ + return call_record; +} diff --git a/src/gui/historylistview.h b/src/gui/historylistview.h new file mode 100644 index 0000000..acde247 --- /dev/null +++ b/src/gui/historylistview.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _HISTORYLISTVIEW_H +#define _HISTORYLISTVIEW_H + +#include +#include "qlistview.h" +#include "qpainter.h" +#include "call_history.h" +#include "user.h" + +// Columns of the history list view +#define HISTCOL_TIMESTAMP 0 +#define HISTCOL_DIRECTION 1 +#define HISTCOL_FROMTO 2 +#define HISTCOL_SUBJECT 3 +#define HISTCOL_STATUS 4 + +class HistoryListViewItem : public QListViewItem { +private: + t_call_record call_record; + time_t last_viewed; + +public: + HistoryListViewItem( QListView * parent, const t_call_record &cr, t_user *user_config, + time_t _last_viewed); + + void paintCell(QPainter *painter, const QColorGroup &cg, + int column, int width, int align); + int compare ( QListViewItem * i, int col, bool ascending ) const; + time_t get_time_start(void) const; + t_call_record get_call_record(void) const; +}; + +#endif + diff --git a/src/gui/images/1downarrow.png b/src/gui/images/1downarrow.png new file mode 100644 index 0000000..d769883 Binary files /dev/null and b/src/gui/images/1downarrow.png differ diff --git a/src/gui/images/1leftarrow-yellow.png b/src/gui/images/1leftarrow-yellow.png new file mode 100644 index 0000000..3e47631 Binary files /dev/null and b/src/gui/images/1leftarrow-yellow.png differ diff --git a/src/gui/images/1leftarrow.png b/src/gui/images/1leftarrow.png new file mode 100644 index 0000000..ac907ef Binary files /dev/null and b/src/gui/images/1leftarrow.png differ diff --git a/src/gui/images/1rightarrow.png b/src/gui/images/1rightarrow.png new file mode 100644 index 0000000..d8b9df1 Binary files /dev/null and b/src/gui/images/1rightarrow.png differ diff --git a/src/gui/images/1uparrow.png b/src/gui/images/1uparrow.png new file mode 100644 index 0000000..62e9a08 Binary files /dev/null and b/src/gui/images/1uparrow.png differ diff --git a/src/gui/images/answer-disabled.png b/src/gui/images/answer-disabled.png new file mode 100644 index 0000000..de1dafd Binary files /dev/null and b/src/gui/images/answer-disabled.png differ diff --git a/src/gui/images/answer.png b/src/gui/images/answer.png new file mode 100644 index 0000000..6ecffaf Binary files /dev/null and b/src/gui/images/answer.png differ diff --git a/src/gui/images/attach.png b/src/gui/images/attach.png new file mode 100644 index 0000000..7b1fbf5 Binary files /dev/null and b/src/gui/images/attach.png differ diff --git a/src/gui/images/auto_answer-disabled.png b/src/gui/images/auto_answer-disabled.png new file mode 100644 index 0000000..1234c0f Binary files /dev/null and b/src/gui/images/auto_answer-disabled.png differ diff --git a/src/gui/images/auto_answer.png b/src/gui/images/auto_answer.png new file mode 100644 index 0000000..c2fa3a4 Binary files /dev/null and b/src/gui/images/auto_answer.png differ diff --git a/src/gui/images/buddy.png b/src/gui/images/buddy.png new file mode 100644 index 0000000..f963a02 Binary files /dev/null and b/src/gui/images/buddy.png differ diff --git a/src/gui/images/bye-disabled.png b/src/gui/images/bye-disabled.png new file mode 100644 index 0000000..8d99eb1 Binary files /dev/null and b/src/gui/images/bye-disabled.png differ diff --git a/src/gui/images/bye.png b/src/gui/images/bye.png new file mode 100644 index 0000000..04cc870 Binary files /dev/null and b/src/gui/images/bye.png differ diff --git a/src/gui/images/cancel-disabled.png b/src/gui/images/cancel-disabled.png new file mode 100644 index 0000000..f874dbb Binary files /dev/null and b/src/gui/images/cancel-disabled.png differ diff --git a/src/gui/images/cancel.png b/src/gui/images/cancel.png new file mode 100644 index 0000000..6b990a2 Binary files /dev/null and b/src/gui/images/cancel.png differ diff --git a/src/gui/images/cf-disabled.png b/src/gui/images/cf-disabled.png new file mode 100644 index 0000000..2392d44 Binary files /dev/null and b/src/gui/images/cf-disabled.png differ diff --git a/src/gui/images/cf.png b/src/gui/images/cf.png new file mode 100644 index 0000000..c0e5c8a Binary files /dev/null and b/src/gui/images/cf.png differ diff --git a/src/gui/images/clock.png b/src/gui/images/clock.png new file mode 100644 index 0000000..332b9ec Binary files /dev/null and b/src/gui/images/clock.png differ diff --git a/src/gui/images/conf-disabled.png b/src/gui/images/conf-disabled.png new file mode 100644 index 0000000..64fef5a Binary files /dev/null and b/src/gui/images/conf-disabled.png differ diff --git a/src/gui/images/conf.png b/src/gui/images/conf.png new file mode 100644 index 0000000..73464c6 Binary files /dev/null and b/src/gui/images/conf.png differ diff --git a/src/gui/images/conference-disabled.png b/src/gui/images/conference-disabled.png new file mode 100644 index 0000000..ed96c6e Binary files /dev/null and b/src/gui/images/conference-disabled.png differ diff --git a/src/gui/images/conference.png b/src/gui/images/conference.png new file mode 100644 index 0000000..ae4b0d9 Binary files /dev/null and b/src/gui/images/conference.png differ diff --git a/src/gui/images/consult-xfer.png b/src/gui/images/consult-xfer.png new file mode 100644 index 0000000..25dc786 Binary files /dev/null and b/src/gui/images/consult-xfer.png differ diff --git a/src/gui/images/contexthelp.png b/src/gui/images/contexthelp.png new file mode 100644 index 0000000..e6d8b10 Binary files /dev/null and b/src/gui/images/contexthelp.png differ diff --git a/src/gui/images/dtmf-0.png b/src/gui/images/dtmf-0.png new file mode 100644 index 0000000..a6ddee3 Binary files /dev/null and b/src/gui/images/dtmf-0.png differ diff --git a/src/gui/images/dtmf-1.png b/src/gui/images/dtmf-1.png new file mode 100644 index 0000000..7093903 Binary files /dev/null and b/src/gui/images/dtmf-1.png differ diff --git a/src/gui/images/dtmf-2.png b/src/gui/images/dtmf-2.png new file mode 100644 index 0000000..90d64e3 Binary files /dev/null and b/src/gui/images/dtmf-2.png differ diff --git a/src/gui/images/dtmf-3.png b/src/gui/images/dtmf-3.png new file mode 100644 index 0000000..a2e1c8c Binary files /dev/null and b/src/gui/images/dtmf-3.png differ diff --git a/src/gui/images/dtmf-4.png b/src/gui/images/dtmf-4.png new file mode 100644 index 0000000..73b8b99 Binary files /dev/null and b/src/gui/images/dtmf-4.png differ diff --git a/src/gui/images/dtmf-5.png b/src/gui/images/dtmf-5.png new file mode 100644 index 0000000..1231f81 Binary files /dev/null and b/src/gui/images/dtmf-5.png differ diff --git a/src/gui/images/dtmf-6.png b/src/gui/images/dtmf-6.png new file mode 100644 index 0000000..b98cd08 Binary files /dev/null and b/src/gui/images/dtmf-6.png differ diff --git a/src/gui/images/dtmf-7.png b/src/gui/images/dtmf-7.png new file mode 100644 index 0000000..e49b7f5 Binary files /dev/null and b/src/gui/images/dtmf-7.png differ diff --git a/src/gui/images/dtmf-8.png b/src/gui/images/dtmf-8.png new file mode 100644 index 0000000..94016db Binary files /dev/null and b/src/gui/images/dtmf-8.png differ diff --git a/src/gui/images/dtmf-9.png b/src/gui/images/dtmf-9.png new file mode 100644 index 0000000..dc797ba Binary files /dev/null and b/src/gui/images/dtmf-9.png differ diff --git a/src/gui/images/dtmf-a.png b/src/gui/images/dtmf-a.png new file mode 100644 index 0000000..2b6f2d1 Binary files /dev/null and b/src/gui/images/dtmf-a.png differ diff --git a/src/gui/images/dtmf-b.png b/src/gui/images/dtmf-b.png new file mode 100644 index 0000000..dc499e3 Binary files /dev/null and b/src/gui/images/dtmf-b.png differ diff --git a/src/gui/images/dtmf-c.png b/src/gui/images/dtmf-c.png new file mode 100644 index 0000000..5246425 Binary files /dev/null and b/src/gui/images/dtmf-c.png differ diff --git a/src/gui/images/dtmf-d.png b/src/gui/images/dtmf-d.png new file mode 100644 index 0000000..2b48cf4 Binary files /dev/null and b/src/gui/images/dtmf-d.png differ diff --git a/src/gui/images/dtmf-disabled.png b/src/gui/images/dtmf-disabled.png new file mode 100644 index 0000000..c4a7a94 Binary files /dev/null and b/src/gui/images/dtmf-disabled.png differ diff --git a/src/gui/images/dtmf-pound.png b/src/gui/images/dtmf-pound.png new file mode 100644 index 0000000..ab1344b Binary files /dev/null and b/src/gui/images/dtmf-pound.png differ diff --git a/src/gui/images/dtmf-star.png b/src/gui/images/dtmf-star.png new file mode 100644 index 0000000..b9ec145 Binary files /dev/null and b/src/gui/images/dtmf-star.png differ diff --git a/src/gui/images/dtmf.png b/src/gui/images/dtmf.png new file mode 100644 index 0000000..92df50f Binary files /dev/null and b/src/gui/images/dtmf.png differ diff --git a/src/gui/images/edit.png b/src/gui/images/edit.png new file mode 100644 index 0000000..4afccb2 Binary files /dev/null and b/src/gui/images/edit.png differ diff --git a/src/gui/images/edit16.png b/src/gui/images/edit16.png new file mode 100644 index 0000000..be9ac1c Binary files /dev/null and b/src/gui/images/edit16.png differ diff --git a/src/gui/images/editcopy b/src/gui/images/editcopy new file mode 100644 index 0000000..7b334ca Binary files /dev/null and b/src/gui/images/editcopy differ diff --git a/src/gui/images/editcut b/src/gui/images/editcut new file mode 100644 index 0000000..60abc58 Binary files /dev/null and b/src/gui/images/editcut differ diff --git a/src/gui/images/editdelete.png b/src/gui/images/editdelete.png new file mode 100644 index 0000000..f617a3d Binary files /dev/null and b/src/gui/images/editdelete.png differ diff --git a/src/gui/images/editpaste b/src/gui/images/editpaste new file mode 100644 index 0000000..19c2fec Binary files /dev/null and b/src/gui/images/editpaste differ diff --git a/src/gui/images/encrypted-disabled.png b/src/gui/images/encrypted-disabled.png new file mode 100644 index 0000000..96d39cb Binary files /dev/null and b/src/gui/images/encrypted-disabled.png differ diff --git a/src/gui/images/encrypted.png b/src/gui/images/encrypted.png new file mode 100644 index 0000000..cd638f0 Binary files /dev/null and b/src/gui/images/encrypted.png differ diff --git a/src/gui/images/encrypted32.png b/src/gui/images/encrypted32.png new file mode 100644 index 0000000..f6db914 Binary files /dev/null and b/src/gui/images/encrypted32.png differ diff --git a/src/gui/images/encrypted_verified.png b/src/gui/images/encrypted_verified.png new file mode 100644 index 0000000..71d6734 Binary files /dev/null and b/src/gui/images/encrypted_verified.png differ diff --git a/src/gui/images/exit.png b/src/gui/images/exit.png new file mode 100644 index 0000000..4ce275d Binary files /dev/null and b/src/gui/images/exit.png differ diff --git a/src/gui/images/favorites.png b/src/gui/images/favorites.png new file mode 100644 index 0000000..7e02468 Binary files /dev/null and b/src/gui/images/favorites.png differ diff --git a/src/gui/images/filenew b/src/gui/images/filenew new file mode 100644 index 0000000..9de6e83 Binary files /dev/null and b/src/gui/images/filenew differ diff --git a/src/gui/images/fileopen-disabled.png b/src/gui/images/fileopen-disabled.png new file mode 100644 index 0000000..96dbb4c Binary files /dev/null and b/src/gui/images/fileopen-disabled.png differ diff --git a/src/gui/images/fileopen.png b/src/gui/images/fileopen.png new file mode 100644 index 0000000..b56225a Binary files /dev/null and b/src/gui/images/fileopen.png differ diff --git a/src/gui/images/filesave b/src/gui/images/filesave new file mode 100644 index 0000000..f6d9af9 Binary files /dev/null and b/src/gui/images/filesave differ diff --git a/src/gui/images/gear.png b/src/gui/images/gear.png new file mode 100644 index 0000000..401549d Binary files /dev/null and b/src/gui/images/gear.png differ diff --git a/src/gui/images/hold-disabled.png b/src/gui/images/hold-disabled.png new file mode 100644 index 0000000..1f8f5de Binary files /dev/null and b/src/gui/images/hold-disabled.png differ diff --git a/src/gui/images/hold.png b/src/gui/images/hold.png new file mode 100644 index 0000000..6cd6db7 Binary files /dev/null and b/src/gui/images/hold.png differ diff --git a/src/gui/images/invite-disabled.png b/src/gui/images/invite-disabled.png new file mode 100644 index 0000000..6197d2f Binary files /dev/null and b/src/gui/images/invite-disabled.png differ diff --git a/src/gui/images/invite.png b/src/gui/images/invite.png new file mode 100644 index 0000000..765f253 Binary files /dev/null and b/src/gui/images/invite.png differ diff --git a/src/gui/images/kcmpci.png b/src/gui/images/kcmpci.png new file mode 100644 index 0000000..a3ddf40 Binary files /dev/null and b/src/gui/images/kcmpci.png differ diff --git a/src/gui/images/kcmpci16.png b/src/gui/images/kcmpci16.png new file mode 100644 index 0000000..0ac401c Binary files /dev/null and b/src/gui/images/kcmpci16.png differ diff --git a/src/gui/images/kmix.png b/src/gui/images/kmix.png new file mode 100644 index 0000000..ba400b7 Binary files /dev/null and b/src/gui/images/kmix.png differ diff --git a/src/gui/images/knotify.png b/src/gui/images/knotify.png new file mode 100644 index 0000000..e5b0cad Binary files /dev/null and b/src/gui/images/knotify.png differ diff --git a/src/gui/images/kontact_contacts-disabled.png b/src/gui/images/kontact_contacts-disabled.png new file mode 100644 index 0000000..a86b960 Binary files /dev/null and b/src/gui/images/kontact_contacts-disabled.png differ diff --git a/src/gui/images/kontact_contacts.png b/src/gui/images/kontact_contacts.png new file mode 100644 index 0000000..68c6582 Binary files /dev/null and b/src/gui/images/kontact_contacts.png differ diff --git a/src/gui/images/kontact_contacts32.png b/src/gui/images/kontact_contacts32.png new file mode 100644 index 0000000..8f8b460 Binary files /dev/null and b/src/gui/images/kontact_contacts32.png differ diff --git a/src/gui/images/log.png b/src/gui/images/log.png new file mode 100644 index 0000000..29d8214 Binary files /dev/null and b/src/gui/images/log.png differ diff --git a/src/gui/images/log_small.png b/src/gui/images/log_small.png new file mode 100644 index 0000000..41b3f43 Binary files /dev/null and b/src/gui/images/log_small.png differ diff --git a/src/gui/images/message.png b/src/gui/images/message.png new file mode 100644 index 0000000..744c451 Binary files /dev/null and b/src/gui/images/message.png differ diff --git a/src/gui/images/message32.png b/src/gui/images/message32.png new file mode 100644 index 0000000..602f1e0 Binary files /dev/null and b/src/gui/images/message32.png differ diff --git a/src/gui/images/mime_application.png b/src/gui/images/mime_application.png new file mode 100644 index 0000000..f274fd4 Binary files /dev/null and b/src/gui/images/mime_application.png differ diff --git a/src/gui/images/mime_audio.png b/src/gui/images/mime_audio.png new file mode 100644 index 0000000..9fefada Binary files /dev/null and b/src/gui/images/mime_audio.png differ diff --git a/src/gui/images/mime_image.png b/src/gui/images/mime_image.png new file mode 100644 index 0000000..d6a9bc3 Binary files /dev/null and b/src/gui/images/mime_image.png differ diff --git a/src/gui/images/mime_text.png b/src/gui/images/mime_text.png new file mode 100644 index 0000000..c17e3c8 Binary files /dev/null and b/src/gui/images/mime_text.png differ diff --git a/src/gui/images/mime_video.png b/src/gui/images/mime_video.png new file mode 100644 index 0000000..de00f0e Binary files /dev/null and b/src/gui/images/mime_video.png differ diff --git a/src/gui/images/missed-disabled.png b/src/gui/images/missed-disabled.png new file mode 100644 index 0000000..a95583a Binary files /dev/null and b/src/gui/images/missed-disabled.png differ diff --git a/src/gui/images/missed.png b/src/gui/images/missed.png new file mode 100644 index 0000000..30e38f2 Binary files /dev/null and b/src/gui/images/missed.png differ diff --git a/src/gui/images/mute-disabled.png b/src/gui/images/mute-disabled.png new file mode 100644 index 0000000..3f35181 Binary files /dev/null and b/src/gui/images/mute-disabled.png differ diff --git a/src/gui/images/mute.png b/src/gui/images/mute.png new file mode 100644 index 0000000..f721deb Binary files /dev/null and b/src/gui/images/mute.png differ diff --git a/src/gui/images/mwi_failure16.png b/src/gui/images/mwi_failure16.png new file mode 100644 index 0000000..4cd5f7f Binary files /dev/null and b/src/gui/images/mwi_failure16.png differ diff --git a/src/gui/images/mwi_new16.png b/src/gui/images/mwi_new16.png new file mode 100644 index 0000000..3affbd0 Binary files /dev/null and b/src/gui/images/mwi_new16.png differ diff --git a/src/gui/images/mwi_none.png b/src/gui/images/mwi_none.png new file mode 100644 index 0000000..39151d6 Binary files /dev/null and b/src/gui/images/mwi_none.png differ diff --git a/src/gui/images/mwi_none16.png b/src/gui/images/mwi_none16.png new file mode 100644 index 0000000..d69df60 Binary files /dev/null and b/src/gui/images/mwi_none16.png differ diff --git a/src/gui/images/mwi_none16_dis.png b/src/gui/images/mwi_none16_dis.png new file mode 100644 index 0000000..bf12682 Binary files /dev/null and b/src/gui/images/mwi_none16_dis.png differ diff --git a/src/gui/images/network.png b/src/gui/images/network.png new file mode 100644 index 0000000..fcdbed0 Binary files /dev/null and b/src/gui/images/network.png differ diff --git a/src/gui/images/no-indication.png b/src/gui/images/no-indication.png new file mode 100644 index 0000000..0656c4e Binary files /dev/null and b/src/gui/images/no-indication.png differ diff --git a/src/gui/images/ok.png b/src/gui/images/ok.png new file mode 100644 index 0000000..543710f Binary files /dev/null and b/src/gui/images/ok.png differ diff --git a/src/gui/images/package_network.png b/src/gui/images/package_network.png new file mode 100644 index 0000000..556e8ff Binary files /dev/null and b/src/gui/images/package_network.png differ diff --git a/src/gui/images/package_system.png b/src/gui/images/package_system.png new file mode 100644 index 0000000..a5d6213 Binary files /dev/null and b/src/gui/images/package_system.png differ diff --git a/src/gui/images/password.png b/src/gui/images/password.png new file mode 100644 index 0000000..9df4840 Binary files /dev/null and b/src/gui/images/password.png differ diff --git a/src/gui/images/penguin-small.png b/src/gui/images/penguin-small.png new file mode 100644 index 0000000..a99e611 Binary files /dev/null and b/src/gui/images/penguin-small.png differ diff --git a/src/gui/images/penguin.png b/src/gui/images/penguin.png new file mode 100644 index 0000000..3d7aa52 Binary files /dev/null and b/src/gui/images/penguin.png differ diff --git a/src/gui/images/penguin_big.png b/src/gui/images/penguin_big.png new file mode 100644 index 0000000..1dc2da6 Binary files /dev/null and b/src/gui/images/penguin_big.png differ diff --git a/src/gui/images/presence.png b/src/gui/images/presence.png new file mode 100644 index 0000000..3d9c866 Binary files /dev/null and b/src/gui/images/presence.png differ diff --git a/src/gui/images/presence_failed.png b/src/gui/images/presence_failed.png new file mode 100644 index 0000000..c4e28d6 Binary files /dev/null and b/src/gui/images/presence_failed.png differ diff --git a/src/gui/images/presence_offline.png b/src/gui/images/presence_offline.png new file mode 100644 index 0000000..28a3738 Binary files /dev/null and b/src/gui/images/presence_offline.png differ diff --git a/src/gui/images/presence_online.png b/src/gui/images/presence_online.png new file mode 100644 index 0000000..f42ff86 Binary files /dev/null and b/src/gui/images/presence_online.png differ diff --git a/src/gui/images/presence_rejected.png b/src/gui/images/presence_rejected.png new file mode 100644 index 0000000..4979190 Binary files /dev/null and b/src/gui/images/presence_rejected.png differ diff --git a/src/gui/images/presence_unknown.png b/src/gui/images/presence_unknown.png new file mode 100644 index 0000000..fb225c7 Binary files /dev/null and b/src/gui/images/presence_unknown.png differ diff --git a/src/gui/images/print b/src/gui/images/print new file mode 100644 index 0000000..1812266 Binary files /dev/null and b/src/gui/images/print differ diff --git a/src/gui/images/qt-logo.png b/src/gui/images/qt-logo.png new file mode 100644 index 0000000..50a79e1 Binary files /dev/null and b/src/gui/images/qt-logo.png differ diff --git a/src/gui/images/redial-disabled.png b/src/gui/images/redial-disabled.png new file mode 100644 index 0000000..5bfbf9b Binary files /dev/null and b/src/gui/images/redial-disabled.png differ diff --git a/src/gui/images/redial.png b/src/gui/images/redial.png new file mode 100644 index 0000000..2e273cf Binary files /dev/null and b/src/gui/images/redial.png differ diff --git a/src/gui/images/redirect-disabled.png b/src/gui/images/redirect-disabled.png new file mode 100644 index 0000000..04348d5 Binary files /dev/null and b/src/gui/images/redirect-disabled.png differ diff --git a/src/gui/images/redirect.png b/src/gui/images/redirect.png new file mode 100644 index 0000000..e99e1b8 Binary files /dev/null and b/src/gui/images/redirect.png differ diff --git a/src/gui/images/redo b/src/gui/images/redo new file mode 100644 index 0000000..f4b348e Binary files /dev/null and b/src/gui/images/redo differ diff --git a/src/gui/images/reg-query.png b/src/gui/images/reg-query.png new file mode 100644 index 0000000..2d0ad1a Binary files /dev/null and b/src/gui/images/reg-query.png differ diff --git a/src/gui/images/reg_failed-disabled.png b/src/gui/images/reg_failed-disabled.png new file mode 100644 index 0000000..b14498a Binary files /dev/null and b/src/gui/images/reg_failed-disabled.png differ diff --git a/src/gui/images/reg_failed.png b/src/gui/images/reg_failed.png new file mode 100644 index 0000000..7042871 Binary files /dev/null and b/src/gui/images/reg_failed.png differ diff --git a/src/gui/images/reject-disabled.png b/src/gui/images/reject-disabled.png new file mode 100644 index 0000000..da19ffd Binary files /dev/null and b/src/gui/images/reject-disabled.png differ diff --git a/src/gui/images/reject.png b/src/gui/images/reject.png new file mode 100644 index 0000000..dbffecd Binary files /dev/null and b/src/gui/images/reject.png differ diff --git a/src/gui/images/save_as.png b/src/gui/images/save_as.png new file mode 100644 index 0000000..41b3f43 Binary files /dev/null and b/src/gui/images/save_as.png differ diff --git a/src/gui/images/searchfind b/src/gui/images/searchfind new file mode 100644 index 0000000..7aaefe2 Binary files /dev/null and b/src/gui/images/searchfind differ diff --git a/src/gui/images/settings.png b/src/gui/images/settings.png new file mode 100644 index 0000000..8bc9a0f Binary files /dev/null and b/src/gui/images/settings.png differ diff --git a/src/gui/images/stat_conference.png b/src/gui/images/stat_conference.png new file mode 100644 index 0000000..8006127 Binary files /dev/null and b/src/gui/images/stat_conference.png differ diff --git a/src/gui/images/stat_established.png b/src/gui/images/stat_established.png new file mode 100644 index 0000000..a27c899 Binary files /dev/null and b/src/gui/images/stat_established.png differ diff --git a/src/gui/images/stat_established_nomedia.png b/src/gui/images/stat_established_nomedia.png new file mode 100644 index 0000000..e278c45 Binary files /dev/null and b/src/gui/images/stat_established_nomedia.png differ diff --git a/src/gui/images/stat_mute.png b/src/gui/images/stat_mute.png new file mode 100644 index 0000000..2729b10 Binary files /dev/null and b/src/gui/images/stat_mute.png differ diff --git a/src/gui/images/stat_outgoing.png b/src/gui/images/stat_outgoing.png new file mode 100644 index 0000000..5b5e8b5 Binary files /dev/null and b/src/gui/images/stat_outgoing.png differ diff --git a/src/gui/images/stat_ringing.png b/src/gui/images/stat_ringing.png new file mode 100644 index 0000000..9a615fa Binary files /dev/null and b/src/gui/images/stat_ringing.png differ diff --git a/src/gui/images/sys_auto_ans.png b/src/gui/images/sys_auto_ans.png new file mode 100644 index 0000000..55a653a Binary files /dev/null and b/src/gui/images/sys_auto_ans.png differ diff --git a/src/gui/images/sys_auto_ans_dis.png b/src/gui/images/sys_auto_ans_dis.png new file mode 100644 index 0000000..ce3449a Binary files /dev/null and b/src/gui/images/sys_auto_ans_dis.png differ diff --git a/src/gui/images/sys_busy_estab.png b/src/gui/images/sys_busy_estab.png new file mode 100644 index 0000000..828385a Binary files /dev/null and b/src/gui/images/sys_busy_estab.png differ diff --git a/src/gui/images/sys_busy_estab_dis.png b/src/gui/images/sys_busy_estab_dis.png new file mode 100644 index 0000000..07a00b2 Binary files /dev/null and b/src/gui/images/sys_busy_estab_dis.png differ diff --git a/src/gui/images/sys_busy_trans.png b/src/gui/images/sys_busy_trans.png new file mode 100644 index 0000000..ab89a47 Binary files /dev/null and b/src/gui/images/sys_busy_trans.png differ diff --git a/src/gui/images/sys_busy_trans_dis.png b/src/gui/images/sys_busy_trans_dis.png new file mode 100644 index 0000000..22daf77 Binary files /dev/null and b/src/gui/images/sys_busy_trans_dis.png differ diff --git a/src/gui/images/sys_dnd.png b/src/gui/images/sys_dnd.png new file mode 100644 index 0000000..dbb7526 Binary files /dev/null and b/src/gui/images/sys_dnd.png differ diff --git a/src/gui/images/sys_dnd_dis.png b/src/gui/images/sys_dnd_dis.png new file mode 100644 index 0000000..4d3cd2c Binary files /dev/null and b/src/gui/images/sys_dnd_dis.png differ diff --git a/src/gui/images/sys_encrypted.png b/src/gui/images/sys_encrypted.png new file mode 100644 index 0000000..00d549b Binary files /dev/null and b/src/gui/images/sys_encrypted.png differ diff --git a/src/gui/images/sys_encrypted_dis.png b/src/gui/images/sys_encrypted_dis.png new file mode 100644 index 0000000..9572071 Binary files /dev/null and b/src/gui/images/sys_encrypted_dis.png differ diff --git a/src/gui/images/sys_encrypted_verified.png b/src/gui/images/sys_encrypted_verified.png new file mode 100644 index 0000000..a390812 Binary files /dev/null and b/src/gui/images/sys_encrypted_verified.png differ diff --git a/src/gui/images/sys_encrypted_verified_dis.png b/src/gui/images/sys_encrypted_verified_dis.png new file mode 100644 index 0000000..1e727b1 Binary files /dev/null and b/src/gui/images/sys_encrypted_verified_dis.png differ diff --git a/src/gui/images/sys_hold.png b/src/gui/images/sys_hold.png new file mode 100644 index 0000000..f1001c1 Binary files /dev/null and b/src/gui/images/sys_hold.png differ diff --git a/src/gui/images/sys_hold_dis.png b/src/gui/images/sys_hold_dis.png new file mode 100644 index 0000000..893fef6 Binary files /dev/null and b/src/gui/images/sys_hold_dis.png differ diff --git a/src/gui/images/sys_idle.png b/src/gui/images/sys_idle.png new file mode 100644 index 0000000..a7d1c5b Binary files /dev/null and b/src/gui/images/sys_idle.png differ diff --git a/src/gui/images/sys_idle_dis.png b/src/gui/images/sys_idle_dis.png new file mode 100644 index 0000000..ffa865c Binary files /dev/null and b/src/gui/images/sys_idle_dis.png differ diff --git a/src/gui/images/sys_missed.png b/src/gui/images/sys_missed.png new file mode 100644 index 0000000..c7c7703 Binary files /dev/null and b/src/gui/images/sys_missed.png differ diff --git a/src/gui/images/sys_missed_dis.png b/src/gui/images/sys_missed_dis.png new file mode 100644 index 0000000..454c707 Binary files /dev/null and b/src/gui/images/sys_missed_dis.png differ diff --git a/src/gui/images/sys_mute.png b/src/gui/images/sys_mute.png new file mode 100644 index 0000000..1055d71 Binary files /dev/null and b/src/gui/images/sys_mute.png differ diff --git a/src/gui/images/sys_mute_dis.png b/src/gui/images/sys_mute_dis.png new file mode 100644 index 0000000..7b1d6f1 Binary files /dev/null and b/src/gui/images/sys_mute_dis.png differ diff --git a/src/gui/images/sys_mwi.png b/src/gui/images/sys_mwi.png new file mode 100644 index 0000000..e038be6 Binary files /dev/null and b/src/gui/images/sys_mwi.png differ diff --git a/src/gui/images/sys_mwi_dis.png b/src/gui/images/sys_mwi_dis.png new file mode 100644 index 0000000..037829c Binary files /dev/null and b/src/gui/images/sys_mwi_dis.png differ diff --git a/src/gui/images/sys_redir.png b/src/gui/images/sys_redir.png new file mode 100644 index 0000000..db694b8 Binary files /dev/null and b/src/gui/images/sys_redir.png differ diff --git a/src/gui/images/sys_redir_dis.png b/src/gui/images/sys_redir_dis.png new file mode 100644 index 0000000..09a4453 Binary files /dev/null and b/src/gui/images/sys_redir_dis.png differ diff --git a/src/gui/images/sys_services.png b/src/gui/images/sys_services.png new file mode 100644 index 0000000..1f076e9 Binary files /dev/null and b/src/gui/images/sys_services.png differ diff --git a/src/gui/images/sys_services_dis.png b/src/gui/images/sys_services_dis.png new file mode 100644 index 0000000..94e30cc Binary files /dev/null and b/src/gui/images/sys_services_dis.png differ diff --git a/src/gui/images/telephone-hook.png b/src/gui/images/telephone-hook.png new file mode 100644 index 0000000..91d8489 Binary files /dev/null and b/src/gui/images/telephone-hook.png differ diff --git a/src/gui/images/transfer-disabled.png b/src/gui/images/transfer-disabled.png new file mode 100644 index 0000000..79a62ef Binary files /dev/null and b/src/gui/images/transfer-disabled.png differ diff --git a/src/gui/images/transfer.png b/src/gui/images/transfer.png new file mode 100644 index 0000000..77f296d Binary files /dev/null and b/src/gui/images/transfer.png differ diff --git a/src/gui/images/twinkle16-disabled.png b/src/gui/images/twinkle16-disabled.png new file mode 100644 index 0000000..ea445f7 Binary files /dev/null and b/src/gui/images/twinkle16-disabled.png differ diff --git a/src/gui/images/twinkle16.png b/src/gui/images/twinkle16.png new file mode 100644 index 0000000..c929c95 Binary files /dev/null and b/src/gui/images/twinkle16.png differ diff --git a/src/gui/images/twinkle24.png b/src/gui/images/twinkle24.png new file mode 100644 index 0000000..a7d1c5b Binary files /dev/null and b/src/gui/images/twinkle24.png differ diff --git a/src/gui/images/twinkle32.png b/src/gui/images/twinkle32.png new file mode 100644 index 0000000..8f8fc3a Binary files /dev/null and b/src/gui/images/twinkle32.png differ diff --git a/src/gui/images/twinkle48.png b/src/gui/images/twinkle48.png new file mode 100644 index 0000000..def423d Binary files /dev/null and b/src/gui/images/twinkle48.png differ diff --git a/src/gui/images/undo b/src/gui/images/undo new file mode 100644 index 0000000..7d59658 Binary files /dev/null and b/src/gui/images/undo differ diff --git a/src/gui/images/yast_PhoneTTOffhook.png b/src/gui/images/yast_PhoneTTOffhook.png new file mode 100644 index 0000000..193d3ad Binary files /dev/null and b/src/gui/images/yast_PhoneTTOffhook.png differ diff --git a/src/gui/images/yast_babelfish.png b/src/gui/images/yast_babelfish.png new file mode 100644 index 0000000..39b7dee Binary files /dev/null and b/src/gui/images/yast_babelfish.png differ diff --git a/src/gui/inviteform.ui b/src/gui/inviteform.ui new file mode 100644 index 0000000..b0d7d63 --- /dev/null +++ b/src/gui/inviteform.ui @@ -0,0 +1,369 @@ + +InviteForm + + + InviteForm + + + + 0 + 0 + 592 + 203 + + + + + 5 + 5 + 0 + 0 + + + + Twinkle - Call + + + + unnamed + + + + layout40 + + + + unnamed + + + + inviteTextLabel + + + &To: + + + inviteComboBox + + + + + spacer3 + + + Vertical + + + Expanding + + + + 20 + 23 + + + + + + subjectLineEdit + + + Optionally you can provide a subject here. This might be shown to the callee. + + + + + addressToolButton + + + TabFocus + + + + + + F10 + + + kontact_contacts.png + + + Address book + + + Select an address from the address book. + + + + + spacer24 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + inviteComboBox + + + + 7 + 0 + 0 + 0 + + + + true + + + 10 + + + NoInsertion + + + 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. + + + + + fromComboBox + + + + 7 + 0 + 0 + 0 + + + + The user that will make the call. + + + + + subjectTextLabel + + + &Subject: + + + subjectLineEdit + + + + + fromTextLabel + + + &From: + + + fromComboBox + + + + + + + layout13 + + + + unnamed + + + + hideUserCheckBox + + + &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> + + + + + spacer5 + + + Horizontal + + + Expanding + + + + 181 + 20 + + + + + + + + spacer12 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + layout20 + + + + unnamed + + + + spacer11 + + + Horizontal + + + Expanding + + + + 91 + 20 + + + + + + okPushButton + + + &OK + + + true + + + + + cancelPushButton + + + &Cancel + + + + + + + + + cancelPushButton + clicked() + InviteForm + reject() + + + okPushButton + clicked() + InviteForm + validate() + + + addressToolButton + clicked() + InviteForm + showAddressBook() + + + hideUserCheckBox + clicked() + InviteForm + warnHideUser() + + + + inviteComboBox + subjectLineEdit + hideUserCheckBox + addressToolButton + okPushButton + cancelPushButton + fromComboBox + + + qstring.h + sockets/url.h + getaddressform.h + user.h + phone.h + gui.h + util.h + audits/memman.h + sys_settings.h + qregexp.h + qvalidator.h + inviteform.ui.h + + + extern t_phone *phone; + + + GetAddressForm *getAddressForm; + + + destination(t_user *, const QString &, const t_url &, const QString &, bool) + raw_destination(const QString &) + + + clear() + show( t_user * user_config, const QString & dest, const QString & subject, bool anonymous ) + validate() + addToInviteComboBox( const QString & destination ) + reject() + closeEvent( QCloseEvent * ) + showAddressBook() + selectedAddress( const QString & address ) + warnHideUser( void ) + + + init() + destroy() + + + + diff --git a/src/gui/inviteform.ui.h b/src/gui/inviteform.ui.h new file mode 100644 index 0000000..6e0b6d4 --- /dev/null +++ b/src/gui/inviteform.ui.h @@ -0,0 +1,153 @@ +/**************************************************************************** +** 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, preserving your code. Create 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +void InviteForm::init() +{ + getAddressForm = 0; + + // Set toolbutton icons for disabled options. + setDisabledIcon(addressToolButton, "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->clearEdit(); + 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->text(i) == + user_config->get_profile_name().c_str()) + { + fromComboBox->setCurrentItem(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().ascii()); + + ui->expand_destination(from_user, + inviteComboBox->currentText().stripWhiteSpace().ascii(), + 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(destination, 0); + 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, "select address", 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.ascii(), MSG_WARNING); + + // Do not warn again + sys_config->set_warn_hide_user(false); +} diff --git a/src/gui/lang/Makefile.am b/src/gui/lang/Makefile.am new file mode 100644 index 0000000..c8b4d40 --- /dev/null +++ b/src/gui/lang/Makefile.am @@ -0,0 +1,38 @@ +pkglangdir = $(pkgdatadir)/lang + +pkglang_DATA = \ + twinkle_nl.qm \ + twinkle_de.qm \ + twinkle_cs.qm \ + twinkle_fr.qm \ + twinkle_ru.qm \ + twinkle_sv.qm + +CLEANFILES = $(pkglang_DATA) + +EXTRA_DIST = \ + twinkle_nl.ts \ + twinkle_de.ts \ + twinkle_cs.ts \ + twinkle_fr.ts \ + twinkle_ru.ts \ + twinkle_sv.ts \ + twinkle_xx.ts + +twinkle_nl.qm: $(top_builddir)/src/gui/twinkle $(top_srcdir)/src/gui/twinkle.pro + lrelease $(LRELEASEOPTION) $(srcdir)/twinkle_nl.ts -qm $@ + +twinkle_de.qm: $(top_builddir)/src/gui/twinkle $(top_srcdir)/src/gui/twinkle.pro + lrelease $(LRELEASEOPTION) $(srcdir)/twinkle_de.ts -qm $@ + +twinkle_cs.qm: $(top_builddir)/src/gui/twinkle $(top_srcdir)/src/gui/twinkle.pro + lrelease $(LRELEASEOPTION) $(srcdir)/twinkle_cs.ts -qm $@ + +twinkle_fr.qm: $(top_builddir)/src/gui/twinkle $(top_srcdir)/src/gui/twinkle.pro + lrelease $(LRELEASEOPTION) $(srcdir)/twinkle_fr.ts -qm $@ + +twinkle_ru.qm: $(top_builddir)/src/gui/twinkle $(top_srcdir)/src/gui/twinkle.pro + lrelease $(LRELEASEOPTION) $(srcdir)/twinkle_ru.ts -qm $@ + +twinkle_sv.qm: $(top_builddir)/src/gui/twinkle $(top_srcdir)/src/gui/twinkle.pro + lrelease $(LRELEASEOPTION) $(srcdir)/twinkle_sv.ts -qm $@ diff --git a/src/gui/lang/twinkle_cs.ts b/src/gui/lang/twinkle_cs.ts new file mode 100644 index 0000000..ad71d31 --- /dev/null +++ b/src/gui/lang/twinkle_cs.ts @@ -0,0 +1,6039 @@ + + + + AddressCardForm + + Twinkle - Address Card + Twinkle - Adresářový záznam + + + &Remark: + P&oznámka: + + + Infix name of contact. + Prostřední jméno nebo titul. + + + 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: + &Titul: + + + Phone number or SIP address of contact. + Telefonní číslo nebo SIP adresa kontaktu. + + + Last name of contact. + Příjmení. + + + &Last name: + &Příjmení: + + + &OK + &OK + + + Alt+O + Alt+O + + + &Cancel + Zrušit (Es&c) + + + Alt+C + Alt+C + + + You must fill in a name. + Musíte zadat jméno. + + + You must fill in a phone number or SIP address. + Musíte zadat jméno nebo SIP adresu. + + + + 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 přihlášení vyžadováno. + + + User profile: + Uživatelský profil: + + + User: + Uživatel: + + + &Password: + &Heslo: + + + Your password for authentication. + Vaše přijhlaš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. Pokud ne, zeptejte se na něj vašeho VoIP poskytovatele. + + + &User name: + Uži&vatelské jméno: + + + &OK + &OK + + + &Cancel + Zrušit (Es&c) + + + Login required for realm: + Pro Realm je nutné přihlášení: + + + 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 - Buddy + + + 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 Buddy. + + + &Show availability + &Ukázat dostupnost + + + 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. + Vybrat tuto volbu pokud chcete vidět dostupnost vašeho buddy. Toto bude fungovat pouze pokud váš VoIP poskytovatel nabízí funkci "prezenčního agenta". + + + &Name: + &Jméno: + + + SIP address your buddy. + SIP adresa vašeho buddy. + + + &OK + &OK + + + Alt+O + Alt+O + + + &Cancel + Zrušit (Es&c) + + + Alt+C + Alt+C + + + 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 buddy seznam: %1 + + + + BuddyList + + Availability + Dostupnost + + + unknown + neznámý + + + offline + offline + + + online + online + + + request rejected + požadavek odmítnut + + + not published + nepublikováno + + + failed to publish + publikování selhalho + + + request failed + požadavek selhal + + + Click right to add a buddy. + pravým kliknutím přidat buddyho. + + + + 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 proces pro audio příjem. + + + Failed to create audio transmitter thread. + Nepodařilo se vytvořit proces pro audio přenos. + + + + 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šechny koncové přístroje + + + &OK + &OK + + + &Cancel + Zrušit (Es&c) + + + + DiamondcardProfileForm + + Twinkle - Diamondcard User Profile + + + + Your 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. + + + + &Account ID: + + + + &PIN code: + + + + &Your name: + + + + <p align="center"><u>Sign up for a Diamondcard account</u></p> + + + + &OK + + + + Alt+O + + + + &Cancel + Zrušit (Es&c) + + + Alt+C + + + + Fill in your account ID. + + + + Fill in your PIN code. + + + + A user profile with name %1 already exists. + + + + Your 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> + + + + + DtmfForm + + Twinkle - DTMF + Twinkle - DTMF + + + Keypad + Klávesnice + + + 2 + 2 + + + 3 + 3 + + + Over decadic A. Normally not needed. + Funkční klávesa A. Normálně nepoužívaná. + + + 4 + 4 + + + 5 + 5 + + + 6 + 6 + + + Over decadic B. Normally not needed. + Funkční klávesa B. Používaná zřídka používaná. + + + 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 + Za&vří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 aktivovat současně 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. Tím umožníte nalezení nové a funkční síťové adresy. + + + 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ána. + + + Line %1: no ACK received, call will be terminated. + Linka %1: žádný ACK od protistrany, volání 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 chyba. + + + 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 odpověděla na volání. + + + Line %1: call failed. + Linka %1: Volání selhalo. + + + The call can be redirected to: + Hovor může být přepojen 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 možností: %1 %2 + + + Terminal capabilities of %1 + Schopnosti protistrany %1 + + + Accepted body types: + Akceptované "body types": + + + unknown + neznámý + + + Accepted encodings: + Akceptovaná "encodings": + + + Accepted languages: + Akceptované jazyky: + + + Allowed requests: + Povolené "requests": + + + Supported extensions: + Podporované "extensions": + + + none + žádný + + + End point type: + Typ koncového zařízení: + + + Line %1: call retrieve failed. + Linka %1: Chyba při pokusu o znovunavázání hovoru. + + + %1, registration failed: %2 %3 + %1, Neúspěšné přihlášení: %2 %3 + + + %1, registration succeeded (expires = %2 seconds) + %1, Přihlášení úspěšné (platné na %2 Sek.) + + + %1, registration failed: STUN failure + %1, Přihlášení neúspěšné: STUN chyba + + + %1, de-registration succeeded: %2 %3 + %1, Odhlášení proběhlo: %2 %3 + + + %1, fetching registrations failed: %2 %3 + %1, Chyba při dotazu na registraci: %2 %3 + + + : you are not registered + : Nejste přihlášen + + + : you have the following registrations + : jsou aktivní následující přihlášení + + + : fetching registrations... + : probíhá dotaz na přihlášení... + + + Line %1: redirecting request to + Linka %1: převést dotaz na + + + Redirecting request to: %1 + Převést dotaz na: %1 + + + Line %1: DTMF detected: + Linka %1: detekováno DTMF: + + + invalid DTMF telephone event (%1) + Neplatné vyhodnocení DTMF (%1) + + + Line %1: send DTMF %2 + Linka %1: vyšli DTMF %2 + + + Line %1: far end does not support DTMF telephone events. + Linka %1: Protistrana nepodporuje žádný DTMF dotaz. + + + Line %1: received notification. + Linka %1: Oznámení přijato. + + + Event: %1 + Událost: %1 + + + State: %1 + Stav: %1 + + + Reason: %1 + Příčina: %1 + + + Progress: %1 %2 + Postup: %1 %2 + + + Line %1: call transfer failed. + Linka %1: Přesměrování hovoru selhalo. + + + Line %1: call succesfully transferred. + Linka %1: Hovor byl přesměrován. + + + Line %1: call transfer still in progress. + Linka %1: Přesměrování hovoru probíhá. + + + No further notifications will be received. + Protistrana zastavila zasílání zpráv. + + + Line %1: transferring call to %2 + Linka %1: Přesměrování hovoru na %2 + + + Transfer requested by %1 + Přesněrování hovoru vyžádáno od %1 + + + Line %1: Call transfer failed. Retrieving original call. + Linka %1: Přesměrování hovoru bylo neúspěšné. V původním hovoru bude pokračováno. + + + Redirecting call + Hovor bude přesměrován + + + User profile: + Uživatelský profil: + + + User: + Uživatel: + + + Do you allow the call to be redirected to the following destination? + Chcete dovolit 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, aby jste zde byl neustále dotazován, musíte změnit nastavení v sekci SIP protokol v uživatelském profilu. + + + Redirecting request + Přesměrovat dotaz + + + 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řesměrování hovoru + + + Request to transfer call received from: + Požadavek na přesměrování hovoru přijat od: + + + Do you allow the call to be transferred to the following destination? + Povolit přesměrování hovoru k následujícímu cíli? + + + Info: + Info: + + + Warning: + Upozornění: + + + Critical: + Kritické: + + + Firewall / NAT discovery... + Firewall / NAT Analýza... + + + 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 otevřít konferenci. + + + Override lock file and start anyway? + Ignorovat blokovací soubor 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, Neúspěšné odhlášení: %2 %3 + + + Request to transfer call received. + Protistrana si vyžádala přenos. + + + 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 (SIP protocol) + + + Use domain name to create a unique contact header + Použijte 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 + + + + Cannot open web browser: %1 + + + + Configure your web browser in the system settings. + + + + + GetAddressForm + + Twinkle - Select address + Twinkle - Výběr adres + + + 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+R + + + Reload the list of addresses from KAddressbook. + Znovu načíst seznam adres z KAddressbook. +Zavření a opětovné otevření okna <i>nevede</i> k novému načtení. Změny v adresáři budou v Twinkle viditelné až po aktivaci "Aktualizace". + + + &OK + &OK + + + Alt+O + Alt+O + + + &Cancel + Zrušit (Es&c) + + + Alt+C + Alt+C + + + &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 + &Lokální adresář + + + Remark + Poznámka + + + Contacts in the local address book of Twinkle. + Kontakty v lokálním adresáři Twinkle. + + + &Add + &Přidat + + + Alt+A + Alt+A + + + Add a new contact to the local address book. + Založit v lokálním adresáři nový kontakt. + + + &Delete + &Smazat + + + Alt+D + Alt+D + + + Delete a contact from the local address book. + Smazat vybraný kontakt z lokálního adresáře. + + + &Edit + &Upravit + + + Alt+E + Alt+E + + + Edit a contact from the local address book. + Upavit vybraný kontakt z lokálního adresáře. + + + <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>Jako alternativa je vám k dispozici lokální adresář Twinkles, bez nutnosti mít výše jmenovaný program.</p> + + + + GetProfileNameForm + + Twinkle - Profile name + Twinkle - Jméno uživatelského programu + + + &OK + &OK + + + &Cancel + Zrušit (Es&c) + + + Enter a name for your profile: + Vložte jméno pro nový 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>Jméno pod kterým bude založen nový profil</b> +<br><br> +Profil obsahuje všechna uživateslká nastavení jako je VoIP poskytovatel, uživatelské jméno SIP, Heslo atd. Pokus spustíte Twinkle bude zobrazen seznam všech profilů, ze kterého si lze vybrat ten se kterým má být pracováno. +<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>Před založením prvního profilu je vhodné si nejprve vyřídit registraci u vašeho SIP poskytovatele a poznačit si jaké <b>SIP přístupové parametry</b> jsou k přihlášení potřebné. +</p> + + + Cannot find .twinkle directory in your home directory. + Nelze najít adresář ".twinkle" ve vašem domovském adresáři ("/home/vase-jmeno/"). + + + Profile already exists. + Profil s tímto jménem již existuje. + + + Rename profile '%1' to: + Profil "%1" přejmenovat 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 + Detaily 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škrtnut tuto volbu, pokud mají být signalizovány příchozí hovory. + + + &Outgoing calls + &Odchozí hovory + + + Alt+O + Alt+O + + + Check this option to show outgoing calls. + Pokud je aktivováno, budou zobrazeny jen odchozí hovory. + + + &Answered calls + &Přijaté hovory + + + Alt+A + Alt+A + + + Check this option to show answered calls. + Pokud je aktivováno, budou zobrazeny jen přijaté hovory. + + + &Missed calls + &Zmeškaná volání + + + Alt+M + Alt+M + + + Check this option to show missed calls. + Pokud je aktivováno, budou zobrazeny jen zmeškaná volání. + + + 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 právě aktivovním 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+C + + + 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: + Komu: + + + Reply to: + Odpovědět na: + + + Referred by: + Přes: + + + Subject: + Název: + + + Released by: + Ukončeno: + + + Status: + Status: + + + Far end device: + Zařízení protistrany: + + + User profile: + Uživatelský profil: + + + conversation + Rozhovor + + + Call... + Volat (dvojitý klik)... + + + Delete + Smazat záznam + + + Re: + Odp: + + + Clo&se + Za&vřít + + + Alt+S + Alt+S + + + &Call + &Volat + + + Call selected address. + Volat vybranou adresu. + + + Number of calls: + + + + ### + + + + Total call duration: + + + + + InviteForm + + Twinkle - Call + Twinkle - Volání + + + &To: + Komu (&Telnr): + + + Optionally you can provide a subject here. This might be shown to the callee. + Zde můžete zadat nějaký název, který bude spolu s vaším jménem zobrazen na volané stanici. + + + Address book + Adresář + + + Select an address from the address book. + Vybrat adresu z KDE-Adressbook. + + + 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. + Adresu, kterou chcete volat. Toto může být plnohodnotná SIP adresa jako např. <b>sip:example@example.com</b> nebo jen pouze telefonní číslo. Pokud není zadaná kompletní adresa twinkle ji doplní o doménové jméno aktuálního uživatelského profilu. + + + The user that will make the call. + Uživatelský profil a současně i VoIP poskytovatel, se kterým bylo volání iniciováno. + + + &Subject: + &Předmět: + + + &From: + &Od: + + + &OK + &OK + + + &Cancel + Zrušit (Es&c) + + + &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 VoIP poskytovatelé!</p> + + + Not all SIP providers support identity hiding. Make sure your SIP provider supports it if you really need it. + Ne všichni VoIP poskytovatelé umožňují skrytí identity. Ujistěte se o tom, pokud se na tuto funkci chcete spolehnout. + + + F10 + F10 + + + + LogViewForm + + Twinkle - Log + Twinkle - Log + + + Contents of the current log file (~/.twinkle/twinkle.log) + Obsah aktuálního protokolovacího souboru (~/.twinkle/twinkle.log) + + + &Close + &Zavřít + + + Alt+C + Alt+C + + + C&lear + &Smazat + + + Alt+L + Alt+L + + + Clear the log window. This does <b>not</b> clear the log file itself. + Smazat protokolovací okno. Obsah samotného souboru s protokolem smazán <b>nebude</b>. + + + + MessageForm + + Twinkle - Instant message + Twinkle - instantní 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 např. <b>sip:example@example.com</b> nebo telefonní číslo z kompletní adresy uživatele. Pokud se neuvede celá adresa, Twinkle doplni adresu hodnotou "Domain" z nastaveni uživatele v 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 napsat zprávu a poté pro odeslání stisknout "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. + Selhalo uložení přílohy. + + + %1 is typing a message. + %1 píše zprávu. + + + F10 + F10 + + + Size + + + + + 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 protistrany, kterou chcete volat. Může to být kompletní SIP adresa ve formě <b>sip:example@example.com</b> nebo také telefonní číslo, popř. uživatelské část SIP adresy. Pokud není zadána celá adresa, twinkle doplní chybějící část doménovým jménem aktivního uživatelského profilu. + + + The user that will make the call. + Uživatel, který zahájí volání. + + + &User: + &Profil: + + + Dial + Volat + + + Dial the address. + Volat adresu. + + + Address book + Adresář + + + Select an address from the address book. + Vybrat volané číslo nebo SIP adresu z adresáře. + + + Auto answer indication. + Zobrazení a nastavení služby "Automatické přijmutí hovoru". + + + Call redirect indication. + Zobrazení a nastavení služby "Přesměrování hovoru". + + + Do not disturb indication. + Zobrazení a nastavení služby "Nerušit". + + + Missed call indication. + Zobrazení "Volání v nepřítomnosti" a otevření seznamu volání. + + + Registration status. + Zobrazení stavu přihlášení. + + + Display + Stavové hlášky + + + Line status + Stav linky + + + Line &1: + Linka &1: + + + Alt+1 + Alt+1 + + + Click to switch to line 1. + Zde klikněte pro přepnutí na linku 1. + + + From: + Od: + + + To: + Komu: + + + 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 + SAS - značka (Short authentication string) + + + g711a/g711a + No need to translate + + + + Audio codec + Audio kodek + + + 0:00:00 + 0:00:00 + + + Call duration + Doba trvání hovoru + + + sip:from + No need to translate + sip:od + + + sip:to + No need to translate + sip:komu + + + subject + No need to translate + předmět + + + photo + No need to translate + foto + + + Line &2: + Linka &2: + + + + Alt+2 + Alt+2 + + + Click to switch to line 2. + Naklikněte k přepojení na linku 2 (nebo Alt+2). + + + &File + &Soubor + + + &Edit + &Upravit + + + C&all + &Hovor + + + Activate line + Vybrat linku + + + &Registration + &Přihlášení + + + &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 + Volat - rozšířené zadávání čísel + + + 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řijmutí hovoru + + + Open keypad to enter digits for voice menu's + Otevřít numerickou klávesnici pro zadávání tónových příkazů (např. ovládání telefonních záznamníků) + + + Register + Přihlásit se + + + &Register + &Přihlásit se + + + Deregister + Odhlásit se + + + &Deregister + &Odhlásit se + + + Deregister this device + Odhlásit tento telefon + + + Show registrations + Zobrazit přihlášení + + + &Show registrations + &Zobrazit přihlášení + + + Terminal capabilities + Parametry protistrany + + + Request terminal capabilities from someone + Dotaz na parametry protistrany + + + Do not disturb + Prosím nerušit + + + &Do not disturb + &Prosím 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 prostředí Qt + + + About &Qt + O prostředí &Qt + + + User profile + Uživatelský profil + + + &User profile... + &Uživatelský profil... + + + Join two calls in a 3-way conference + Připojit se ke konferenčnímu hovoru 2 uživatelů + + + Mute a call + Vypnout/zapnout mikrofon + + + Transfer call + Přesměrování hovoru + + + System settings + Systémová nastavení + + + &System settings... + &Systémová nastavení... + + + Deregister all + Odhlásit se od všech účtů + + + Deregister &all + &Odhlásit se od všech účtů + + + Deregister all your registered devices + Odhlásit všechny zařízení pod tímto uživatelským profilem + + + Auto answer + Automaticky přijmout volání + + + &Auto answer + &Automaticky přijmout volání + + + Log + Protokol + + + &Log... + &Protokol... + + + Call history + Seznam všech volání + + + Call &history... + &Seznam všech volání... + + + F9 + F9 + + + Change user ... + Změnit uživatelský profil ... + + + &Change user ... + &Změnit uživatelský profil ... + + + Activate or de-activate users + Aktivovat/deaktivovat uživatelský profil + + + 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áčení + + + attempting call, please wait + pokus o navázání spojení, prosím čekejte + + + incoming call + Příchozí volání + + + establishing call, please wait + Pokus o spojení, prosím čekejte + + + established + Spojení navázáno + + + established (waiting for media) + Spojení navázáno (čeká se na data) + + + releasing call, please wait + Odpojení spojení, prosím čekejte + + + unknown state + neznámý stav + + + Voice is encrypted + Přenos hovoru je zašifrován + + + Click to confirm SAS. + Potvrdit značku SAS. + + + Click to clear SAS verification. + Smazat SAS potvrzení. + + + User: + Uživatel: + + + Call: + Hovor: + + + Registration status: + Stav VOIP registrace: + + + Registered + Registrováno + + + Failed + Nezdařilo se + + + Not registered + Neregistrován + + + No users are registered. + Nepř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: + "Automaticky přijmout" 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. + "Automaticky přijmout" není aktivováno. + + + You have no missed calls. + Žádná volání v nepřítomnosti. + + + You missed 1 call. + 1 zmeškané volání v nepřítomnosti. + + + You missed %1 calls. + %1 volání v nepřítomnosti. + + + Click to see call history for details. + Kliknutím se otevře detailní záznam hovorů. + + + Starting user profiles... + Otevřít uživatelský profil... + + + 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 je možné aktivovat pouze jeden uživatelský profil. + + + 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 + Zpětný dotaz + + + Hide identity + Skrýt identitu + + + Click to show registrations. + Kliknutím se zobrazí VOIP 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 + nezanesený + + + 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é ji nastavit ve vašem 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í čekání na zprávu. + + + Voice mail + Hlasový záznamník + + + &Voice mail + &Hlasový záznamník + + + Access voice mail + Přístup na zvukový záznamník + + + F11 + + + + Buddy list + Buddy seznam + + + &Message + &Zpráva + + + Msg + Msg + + + Instant &message... + Instantní &zpráva... + + + Instant message + Instantní zpráva + + + &Call... + Volat (&Call)... + + + &Edit... + &Upravit... + + + &Delete + &Smazat + + + O&ffline + O&ffline + + + &Online + &Online + + + &Change availability + &Změnit dostupnost + + + &Add buddy... + &Přidat buddy... + + + Failed to save buddy list: %1 + Selhalo uložení seznamu s buddy: %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ý buddy seznam pro každý uživatelský profil. Dostupnost vaších buddy a dostupnost vlastní lze zjistit a využívat jen pokud VoIP poskytovatel provozuje prezenční server. + + + &Buddy list + &Buddy seznam + + + &Display + &Stavové hlášky + + + F10 + F10 + + + Diamondcard + + + + Manual + + + + &Manual + + + + Sign up + + + + &Sign up... + + + + Recharge... + + + + Balance history... + + + + Call history... + + + + Admin center... + + + + Recharge + + + + Balance history + + + + Admin center + + + + + NumberConversionForm + + Twinkle - Number conversion + Twinkle - konverze tel. čísla + + + &Match expression: + &Hledaný výraz: + + + &Replace: + &Nahradit: + + + Perl style format string for the replacement number. + Perl formát řetězce pro nahrazované tel. číslo. + + + Perl style regular expression matching the number format you want to modify. + Regulární výraz (Perl regex) pro nahrazované tel. číslo. + + + &OK + + + + Alt+O + Alt+O + + + &Cancel + Zrušit (Es&c) + + + Alt+C + Alt+C + + + Match expression may not be empty. + Hledaný výraz nesmí být prázdný. + + + Replace value may not be empty. + Nahrazovaná hodnota nesmí být prázdná. + + + Invalid regular expression. + Neplatný regulární výraz. + + + + RedirectForm + + Twinkle - Redirect + Twinkle - Přesměrování volá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. + Vybrat tel. číslo / SIP adresu z adresáře. + + + &OK + + + + &Cancel + Zrušit (Es&c) + + + F10 + F10 + + + F12 + F12 + + + F11 + F11 + + + + SelectNicForm + + Twinkle - Select NIC + Twinkle - výběr síťového připojení + + + 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 standardní 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 standardní. Při přístím startu Twinkle bude zvolena automaticky tato adresa. + + + Set as default &NIC + Nastavit jako &standardní připojení + + + 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 standardní. 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. + Standardní nastavení je možné změnit kdykoliv později v konfiguraci systému. + + + + SelectProfileForm + + Twinkle - Select user profile + Twinkle - výběr uživatelského profilu + + + Select user profile(s) to run: + Výběr uživatelského profilu (popř. profilů), 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 potom "Použít". + + + &New + &nový + + + Create a new profile with the profile editor. + Pomocí editoru profilu založit nový uživatelský profil. + + + &Wizard + &Wizard + + + Alt+W + + + + Create a new profile with the wizard. + Pomocí wizardu založit nový uživatelský profil. + + + &Edit + U&pravit + + + Alt+E + + + + Edit the highlighted profile. + Upravit vybraný uživatelský profil. + + + &Delete + &Smazat + + + Alt+D + Alt+L + + + Delete the highlighted profile. + Smazat vybraný uživatelský profil. + + + Ren&ame + &Přejmenovat + + + Alt+A + Alt+U + + + Rename the highlighted profile. + Das ausgewählte Benutzerprofil umbenennen. + + + &Set as default + Nastavit jako &výchozí + + + Alt+S + + + + Make the selected profiles the default profiles. The next time you start Twinkle, these profiles will be automatically run. + Použít vybrané profily jako standardní. Twinkle je použije automaticky při příštím startu. + + + &Run + &Spustit + + + Alt+R + + + + 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 + + + + Edit the system settings. + Upravit systémová nastavení. + + + &Cancel + Zrušit (Es&c) + + + Alt+C + + + + <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 byste si měli překontrolovat a popřípadě upravit systémová nastavení. Obzvláště potom, zda-li v oblasti Audio je v pořádku nastavení mikrofonu a reproduktorů.<br><br>Klikněte na OK pro přístup k systémovým nastavením. Systémová nastavení můžete upravit i kdykoliv později.</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 uživatelský profil + + + Failed to delete profile. + Chyba při mazání uživatelského profilu. + + + Failed to rename profile. + Chyba při přejmenování uživatelského 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>Standardní 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 skrytý adresář ".twinkle" ve vašem domovském adresáři ("/home/vasejmeno/") . + + + &Profile editor + &Editor profilu + + + 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 + Twinkle - Výběr uživatelského profilu + + + &Cancel + Zrušit (Es&c) + + + Alt+C + Alt+C + + + &Select all + Vybrat &vše + + + Alt+S + Alt+S + + + &OK + + + + Alt+O + Alt+O + + + C&lear all + Vybrat &vše + + + Alt+L + Alt+L + + + purpose + No need to translate + + + + User + Uživatel + + + Register + Přihlásit se + + + Select users that you want to register. + Vybrat uživatelský profil k přihlášení. + + + Deregister + Odhlásit + + + Select users that you want to deregister. + Vybrat uživatelský profil k odhlášení. + + + Deregister all devices + Odhlásit všechny zařízení + + + Select users for which you want to deregister all devices. + Vybrat uživatelský profil od něhož mají být odhlášena všechna zařízení. + + + Do not disturb + Prosím nerušit + + + Select users for which you want to enable 'do not disturb'. + Vybrat uživatelský profil, 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 vzít 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 (Es&c) + + + Alt+C + Alt+C + + + 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>Trvale:</b> přesměrovat všechny hovory +</p> +<p> +<b>Obsazeno:</b> Přesměrovat hovor, pokud jsou obě linky obsazené +</p> +<p> +<b>Žádná odpověď:</b> Přesměrovat volání po uplynutí čekací prodlevy +</p> + + + &Unconditional + &Trvale + + + &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. + Vybrat z adresáře volané číslo / SIP adresu. + + + &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 + &Žádná odpověď + + + &Redirect calls when I do not answer + &Přesměrovat volání, pokud uživatel neodpovídá + + + 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 (Es&c) + + + Alt+C + + + + 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. + Vybrat audio připojení 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. + Vybrat audio připojení pro mikrofon. + + + Select the sound card for the speaker function during a call. + Vybrat audio připojení pro sluchátka/reproduktoru. Může být shodné s audio připojením pro vyzváněcí tón. + + + &Speaker: + &Sluchátka/Reproduktor: + + + &Ring tone: + &Vyzváněcí tón: + + + Other device: + Jiné připojení: + + + &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 OSS &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. + ALSA play perioda ovlivňuje zjednodušeně řečeno velikost paketů, pomocí kterých jsou posílána data na zvukovou kartu. Hodně malých paketů zatěžuje více procesor, ale ztráta nějakého paketu není tolik vážná. Při problémech s vypadáváním nebo přeskakováním zvuku je možné zaexperimentovat s jinými hodnotami. + + + ALSA &play period size: + Velikost ALSA &play (LS) periody: + + + &ALSA capture period size: + Velikost &ALSA capture (MIC) periody: + + + 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. + OSS fragment size ovlivňuje zjednodušeně řečeno velikost paketů, pomocí kterých jsou posílána data na zvukovou kartu. Hodně malých paketů zatěžuje více procesor, ale ztráta nějakého paketu není tolik vážná. Při problémech s vypadáváním nebo přeskakováním zvuku je možně zaexperimentovat s jinými hodnotami. + + + 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 capture periody ovlivňuje zjednodušeně řečeno velikost paketů, pomocí kterých jsou posílána data na zvukovou kartu. Hodně malých paketů zatěžuje více procesor, ale ztráta nějakého paketu není tolik vážná. Při problémech s vypadáváním nebo přeskakováním zvuku je možně zaexperimentovat s jinými hodnotami. + + + &Max log size: + &Maximální velikost systémového 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. + Systémový log je používán experty pro hledání problémů. Zde je možné nastavit jeho maximální velikost. Při dosažení logu této velikosti je twinkle.log přejmenováno na twinkle.old Pokud již soubor twinkle.old existuje, je přepsán. + + + MB + + + + Log &debug reports + Zapsat i '&debug' hlášky + + + Alt+D + + + + 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 přidělení/uvolnění RAM paměti. + + + System tray + Systémová lišta + + + Create &system tray icon on startup + Při spuštění vytvořit &ikonku 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. + S touto volbou vytvoří Twinkle při startu v systémové liště symbol hvězdičky. Přes tento lze kdykoliv program vyvolat do popředí. Rovněž je přes jeho vzhled signalizován aktuální stav telefonní linky. + + + &Hide in system tray when closing main window + Přesunout do systémové lišty při zavření &hlavního okna programu + + + Alt+H + + + + Enable this option if you want Twinkle to hide in the system tray when you close the main window. + Pokud je aktivováno, je twinkle při zavření hlavního okna přesunuto do systémové lišty. K úplnému ukončení programu je nutné vybrat volbu "ukončit" v menu "Soubor" nebo v kontextovém menu symbolu Twinkle 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 startu Twinkle neotevírat hlavní okno, nýbrž spustit zminimalizované do systémové lišty. Měl by být rovněž přednastaven standarní profil, jinak dojde k vyvolání okna s výběrem profilu. + + + 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. + Zde nastavené uživatelské profily budou při startu programu automaticky aktivovány. Kdykoliv je můžete přes "Soubor" -> "Změnit uživatelské profily" aktivovat nebo deaktivovat. + + + Services + Služby + + + Call &waiting + Detekce &příchozího hovoru + + + 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. + Pokud je aktivována funkce "Detekce příchozího hovoru", může být v případě obsazené linky volání přesměrováno na druhou linku. Pokud je funkce deaktivována, je volajícímu signalizováno "obsazeno". + + + Hang up &both lines when ending a 3-way conference call. + Při ukončení konferenčního hovoru budou &obě linky "zavěšeny". + + + 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í" odpojeny 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 &otevřít automaticky hlavní okno + + + 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 zobrazeno do popředí. + + + Number of seconds after which the main window should be shown. + Čas v sekundách po kterém bude hlavní okno programu obnoveno do popředí. + + + 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í &spustit vyzváněcí tón + + + Alt+P + + + + Indicates if a ring tone should be played when a call comes in. + Pokud je aktivováno, spustí Twinkle při příchozím volání vyzváněcí tón přes přednastavené audio připojení. + + + &Default ring tone + &Standardní vyzváněcí tón + + + Play the default ring tone when a call comes in. + Spustí standardní vyzváněcí tón při příchozím volání. + + + C&ustom ring tone + individuá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 jméno .wav souboru pro vlastní vyzváněcí tón. + + + 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 volané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>Tento tón je závislý od vašeho SIP poskytovatele.</p> + + + D&efault ring back tone + &Standardní tón pro vyzvánění u volaného + + + Play the default ring back tone. + Přehrávat standardní tón vyzvánění u volaného. + + + Cu&stom ring back tone + &Individuální tón vyzvánění u volaného + + + Play a custom ring back tone. + Použít individuální 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áš indiviuální tón vyzvánění u volaného. + + + &Lookup name for incoming call + Podle čísla &zjistit 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řevzít změny a uložit. + + + &Cancel + Zrušit (Es&c) + + + Alt+C + + + + Undo all your changes and close the window. + Vrátit zpět všechny změny a uzavřít. + + + 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 pro signalizaci volné linky + + + &Validate devices before usage + Ověřit audio nasta&vení před prvním použití + + + 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>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. + Výběr souboru s vyzváněcím tónem. + + + Select ring back tone file. + Výběr souboru pro vyzváněcí tón 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: + + + + Command to start your web browser. If you leave this field empty Twinkle will try to figure out your default web browser. + + + + + SysTrayPopup + + Answer + Přijmout + + + Reject + Odmítnout + + + Incoming Call + + + + + TermCapForm + + Twinkle - Terminal Capabilities + Twinkle - parametry protistrany + + + &From: + &Od: + + + Get terminal capabilities of + Dotázat se na parametry následující 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 kompletní SIP adresa ve formátu <b>sip:example@example.com</b> nebo jen telefonní číslo celé adresy. Pokud není adresa kompletní, Twinkle doplní jméno domény podle standardního uživatelského profilu. + + + Address book + Adresář + + + Select an address from the address book. + Vybrat z adresáře volané číslo nebo SIP adresu. + + + &OK + + + + &Cancel + Zrušit (Es&c) + + + F10 + F10 + + + + TransferForm + + Twinkle - Transfer + Twinkle - převedení + + + Transfer call to + Převést 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 které má být hovor přesměrován. Může to být kompletní SIP adresa jako např. <b>sip:example@example.com</b> nebo jen telefonní číslo. Pokud není zadána plná SIP adresa, Twinkle doplní adresu jménem domény aktivního uživatelského profilu. + + + Address book + Adresář + + + Select an address from the address book. + Vybrat z adresáře volané číslo nebo SIP adresu. + + + &OK + + + + Alt+O + + + + &Cancel + Zrušit (Es&c) + + + Type of transfer + Typ přesměrování + + + &Blind transfer + &Bez zpětného 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 bylo nutné ho nejdříve kontaktovat. + + + T&ransfer with consultation + Přesměrovat se &zpětným dotazem + + + 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. + Propojit obě protistrany na lince 1 a 2. Protistrana, která je právě na aktivní lince bude ta, která bude hovor přesměrovat. + + + 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 ke čtení soubor "%1" + + + File system error while reading file %1 . + Systémová chyba při čtení souboru "%1". + + + Cannot open file for writing: %1 + Nelze otevřít k zápisu soubor "%1" + + + File system error while writing file %1 . + Systémová chyba 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> + Český překlad vypracoval Marek Straka, ©20090701, (http://marek.straka.info) + + + Directory %1 does not exist. + Složka "%1" nenalezena. + + + Cannot open file %1 . + Soubor "%1" nelze otevřít. + + + %1 is not set to your home directory. + "%1" neukazuje na vaši domovskou složku. + + + Directory %1 (%2) does not exist. + Složka "%1" (%2) neexistuje. + + + Cannot create directory %1 . + Složku "%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áloze z "%1" do "%2" + + + unknown name (device is busy) + Neznámé zařízení nebo je již obsazené + + + Default device + Standardní zařízení + + + Anonymous + Anonymní + + + Warning: + Upozornění: + + + Call transfer - %1 + Přesměrování - %1 + + + Sound card cannot be set to full duplex. + Audio zařízení nelze nastavit na "voll 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ý záznam. + + + 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í ALSA ovladačů + + + Cannot open ALSA driver for PCM playback + Nelze otevřít ALSA ovladač pro PCM přehrávání + + + Cannot resolve STUN server: %1 + Nelze identifikovat URL 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 "symetric NAT". +STUN nebude fungovat. +Je nutné nastavit v uživatelském profilu veřejně dostupnou IP adresu +a na vašem routeru/firewallu/NATu vytvořit veřejně přístupné porty +nasměrované na lokální porty na vašem počítači. + + + public IP: %1 --> private IP: %2 (SIP signaling) + veřejná IP: %1 --> privátní IP: %2 (SIP Protokol) + + + 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 připojit na STUN server: "%1" + + + Port %1 (SIP signaling) + Port %1 (SIP Protokol) + + + NAT type discovery via STUN failed. + Analýza NAT 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). + Audio zařízení "%1" pro vyzváněcí tón není přístupné. + + + Cannot access the speaker (%1). + Audio zařízení "%1" pro reproduktory/sluchátka není přístupné. + + + Cannot access the microphone (%1). + Audio zařízení "%1" pro mikrofon není přístupné. + + + Cannot open ALSA driver for PCM capture + Nelze otevřít ALSA ovladač pro PCM nahrávání + + + Cannot receive incoming TCP connections. + Nelze přijmout 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 . + + + + + UserProfileForm + + Twinkle - User Profile + Twinkle - uživatelský profil + + + User profile: + Uživatelský profil: + + + Select which profile you want to edit. + Vybrat profil k úpravě. + + + User + Uživatel + + + SIP server + SIP Server + + + RTP audio + RTP Audio + + + SIP protocol + SIP protokol + + + NAT + NAT (překlad adres) + + + Address format + Formát adresy + + + Timers + Časovač + + + 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 (Es&c) + + + Alt+C + + + + Undo all your changes and close the window. + Vrátit zpět všechny změny a zavřít okno. + + + SIP account + Údaje SIP účtu + + + &User name*: + Uživatelské &jméno *: + + + &Domain*: + + + + 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 VoIP poskytovatelem. Je první částí vaší kompletní SIP adresy <b>uzivatel</b>@domain.com +Mnozí VoIP poskytovatelé toto označují jako telefonní číslo. +<br><br> +*ÚDAJE V TOMTO ŘÁDKU JSOU 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éna nebo IP adresa, pod kterou jste vedeni u vašeho VoIP poskytovatele, popř. dosažitelný v internetu. Je to druhá část vaší kompletní SIP adresy uzivatel@<b>domain.com</b>, popř. doména vaší SIP proxy. +U mnohých VoIP poskytovatelů je totožná s doménou poskytovatele. +Pro přímé propojení IP-to-IP se zde uvede jméno nebo IP, pod kterým je váš počítač dosažitelný v internetu. +<br><br> +*ÚDAJE V TOMTO ŘÁDKU JSOU 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ě. +Není bezpodmínečně nutné. +Vyhněte se písmenům s diakritikou. Některé VoIP přístroje s tím mohou mít problémy. + + + 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. +Údaj není nutný. +Vyhněte se používání písmen s diakritrikou. Některé VoIP přístroje s tím mohou mít problémy. + + + &Your name: + &Jméno: + + + SIP authentication + SIP přihlašovací údaje + + + &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. + "Realm" hodnota pro přihlášení. Možné přeložit jako "oblast". Tento údaj vám poskytne váš SIP poskytovatel. Pokud zůstane pole prázdné, pokusí se Twinkle při každém Realm dotazu o přihlášení s vaším uživatelským jménem a heslem. + + + 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. Potom jej lze vynechat. Pokud tomu tak není, poskytne vám ho váš SIP poskytovatel. + + + Your password for authentication. + Vaše přihlašovací SIP heslo. Pokud toto políčko zůstane nevyplněné, je nutné heslo zadat vždy při spuštění programu při navázání kontaktu. + + + 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. + Doména, IP adresa nebo jméno vašeho SIP přihlašovacího serveru. Pokud použijete outbond proxy, která je totožná s vaším SIP poskytovatelem, lze toto políčko nechat prázdné a vyplnit pouze adresu outbond proxy. + + + &Expiry: + &Platnost: + + + The registration expiry time that Twinkle will request. + Doba platnosti přihlášení v sekundách, kterou si Twinkle při přihlášení vyžádá. Po uplynutí této doby se Twinkle přihlásí automaticky znovu. Pokud tak neučiní, váš poskytovatel to vyhodnotí jako stav, že je váš telefon offline a tedy nedostupný pro volání. Také změny vaší IP adresy, např. po odpojení a opětovném připojení do sítě s přidělením nové IP adresy jsou brány do úvahy až po uplynutí této doby. +Hodnoty pod 120 nejsou doporučeny. Standardně: 3600 (=1h). + + + seconds + sekund + + + Re&gister at startup + Přihlásit při spuštění &profilu + + + 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. + Pokud je aktivováno, Twinkle se při otevření tohoto uživatelského profilu automaticky pokouší přihlásit k vašemu poskytovateli. Přesněji řečeno k přihlašovacímu SIP serveru. +Pro přímé propojení telefonů IP-to-IP neexistuje žádný poskytovatel a tato volba by měla být vypnutá. + + + Outbound Proxy + + + + &Use outbound proxy + &Použít 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. + Pokud je aktivováno, použije Twinkle outbound proxy pro odcházející volání na kterou budou zasílány SIP dotazy. Outbond proxy je možno přeložit jako "zprostředkovatele". Toto může být např. SIP gateway vaší firemní sítě. +Bez uvedení outbound proxy se Twinkle sám pokusí zjistit IP adresu k volané telefonnímu číslu/adrese. + + + Outbound &proxy: + Outbound &proxy: + + + &Send in-dialog requests to proxy + Poslat In-&Dialog dotaz na 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. + Pokud je aktivováno, jsou SIP dotazy posílány <b>vždy</b> na Outbound-Proxy. Normálně totiž Twinkle posílá SIP dotazy během SIP dialogu (t.j během probíhajícího hovoru) na adresu obdrženou na počátku volání v kontaktní hlavičce. Tedy přímo protistraně. + + + &Don't send a request to proxy if its destination can be resolved locally. + &Neposílat SIP dotazy na proxy, pokud tato není dosažitelná. Dotazy poslat přímo. + + + 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 dotaz bude v tomto případě poslán na proxy, jen pokud je aktivována předchozí volba) + + + The hostname, domain name or IP address of your outbound proxy. + Doménové jméno, IP adresa nebo jméno vaší outbound 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, neaktivovaných kodeků. +V závislosti od nastavení při kompilaci mohou být některé kodeky nepřístupné. + + + 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 v použití. + + + 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 pro použií při navázání spojení. + + + 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. znížit jeho prioritu pro použií při navázání spojení. + + + &G.711/G.726 payload size: + &G.711/G.726 payload hodnota: + + + The preferred payload size for the G.711 and G.726 codecs. + Upřednostňovaná payload hodnota pro kodeky G.711 a G.726. + + + ms + + + + &iLBC + + + + iLBC + + + + i&LBC payload type: + i&LBC payload type: + + + iLBC &payload size (ms): + iLBC &payload &hodnota (ms): + + + The dynamic type value (96 or higher) to be used for iLBC. + Typ dynamické hodnoty pro použití v iLBC (96 nebo více). + + + 20 + + + + 30 + + + + The preferred payload size for iLBC. + Upřednostňovaná velikost payload hodnot RTP paketu 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í kvality zvuku" (anglicky: perceptual enhancement) jsou určité funkce daného kodeku, které mají zajistit potlačení šumu při přihlédnutí k vlastnostem lidského sluchu. Ačkoliv dojde při použití těchto funkcí z hlediska technických parametrů přenosu k jeho zhoršení (S/N odstup šumu) a odchýlení se od originálu, je nakonec pociťováno zlepšení kvality zvuku. + + + &Ultra wide band payload type: + &Ultra wide band payload type: + + + 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: + &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. + Proměnná vzorkovací frekvence (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 a zvláště přestávky v hovoru (viz VAD) vystačí s malým datovým tokem. Díky VBR tak lze při dané datové rychlosti docílit lepší kvality zvuku. Anebo 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í vzorkovací frekvence a ne průměrná. + + + The dynamic type value (96 or higher) to be used for speex wide band. + Dynamická typová hodnota pro speex wide band (ne méně než 96). + + + Co&mplexity: + Ko&mplexita: + + + Discontinuous transmission is an addition to VAD/VBR operation, that allows to stop transmitting completely when the background noise is stationary. + Discontinuous transmission, přeloženo jako nesouvislý datový přenos, je rozšíření VAD/VBR přenosu. Při neměnícím se audio signálu (především v odmlkách mezi slovy) nejsou odesílána stále stejná data nýbrž dojde k úplnému přerušení posílání dat. Vede to k částečnému snížení průměrného datového toku. Při nekvalitním přenosu však tato volba může vést k poruchám zvuku, jako např. k zaseknutí určitého tónu nebo různým zkreslením. + + + The dynamic type value (96 or higher) to be used for speex narrow band. + Dynamická hodnota pro speex narrow band (ne méně než 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. + S použitím funkce Speex může být určena komplexita (přesnost) pro daný kodek. 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é přenos DTMF signálů nebo hudebního signálu. + + + &Narrow band payload type: + &Narrow band payload type: + + + G.726 + + + + G.726 &40 kbps payload type: + payload type hodnota pro G.726 &40 kb/s : + + + The dynamic type value (96 or higher) to be used for G.726 40 kbps. + Dynamická typová hodnota pro G.726 40 kb/s (ne méně než 96). + + + The dynamic type value (96 or higher) to be used for G.726 32 kbps. + Dynamická typová hodnota pro G.726 32 kb/s (ne méně než 96). + + + G.726 &24 kbps payload type: + Payload hodnota pro G.726 &24 kb/s : + + + The dynamic type value (96 or higher) to be used for G.726 24 kbps. + Dynamická typová hodnota pro G.726 24 kb/s (ne méně než 96). + + + G.726 &32 kbps payload type: + Payload type hodnota pro G.726 &32 kb/s : + + + The dynamic type value (96 or higher) to be used for G.726 16 kbps. + Dynamická typová hodnota pro G.726 16 kb/s (ne méně než 96). + + + G.726 &16 kbps payload type: + Payload type hodnota 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á typová hodnota pro (RFC2833) (ne méně než 96). + + + DTMF vo&lume: + DTMF &hlasitost: + + + The power level of the DTMF tone in dB. + Hlasitost vysílaných DTMF tónů v dB. Jak pro reálné tóny tak i pro rozpoznávání hladiny úrovní dle RFC2833. Měla by být mezi -10 až -6. + + + The pause after a DTMF tone. + Doba prodlevy mezi dvěmi DTMF tóny. Příliš malé hodnoty mohou vést k tomu, že dva po sobě následující stejné tóny mohou splynout a nebudou rozpoznány jako dvě stejná čísla. Vyšší hodnoty nejsou na závadu, pokud ovšem není žádáno co nejrychlejší přenos DTMF signálů. + + + DTMF &duration: + DTMF &trvání: + + + DTMF payload &type: + DTMF payload &type: + + + DTMF &pause: + DTMF &pauza: + + + dB + + + + Duration of a DTMF tone. + Doba trvání jednoho DTMF tónu v milisekundách. Při příliš malé hodnotě nemusí přijímací stanice odpovídající "hodnoty" správně rozpoznat. Hodnota 200 by měla vyhovovat u většiny i starších přístrojů. + + + DTMF t&ransport: + DTMF &režim: + + + 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> +Vysílá DTMF tóny jako RFC 2833 telefonní signály (symboly v RTP datovém audio toku).</p> +<p><h3>Inband</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>Out-of-band (SIP INFO)</h3> +Vysílá DTMF out-of-band pouze přes SIP INFO požadavek.</p> + + + General + Obecné + + + Redirection + Přesměrování pro odchozí volání + + + &Allow redirection + Povolit &přesměrování + + + Alt+A + + + + Indicates if Twinkle should redirect a request if a 3XX response is received. + Pokud je aktivováno, Twinkle sleduje zda volaná protistrana vyšle požadavek (3XX). Pokud ano, dojde k přesměrování. + + + 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, zdali 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í (z A do B do C atd.), po kterém Twinkle ukončí pokusy navázat spojení. Zabraňuje zacyklení při přesměrování (A -> B -> A...). + + + Protocol options + Nastavení SIP protokolu + + + Call &Hold variant: + Standard volání a &podržení volání: + + + 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. + Výběr zdali bude k podržení hovoru použito RFC 2543 (set media IP address in SDP to 0.0.0.0) nebo RFC 3264 (use direction attributes in 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 kontaktní hlavičku. Někteří poskytovatelé buď neposílají kontaktní hlavičku anebo posílají chybnou kontaktní hlavičku. Pokud je aktivováno, pokusí se Twinkle tuto chybu opravit. + + + &Max-Forwards header is mandatory + &Max-Forwards-Header je vyžadován + + + 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 Max-Forwards header povinný. Často však není posílán. Pokud je aktivováno, odmítne Twinkle SIP požadavky, které neobsahují Max-Forwards header. + + + 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 REGISTER požadavku může být přenášena jak v Contact-Header tak i v Expires-Header. Pokud je aktivováno, posílá ji Twinkle v Contact-header. Jinak v Expires-header. + + + &Use compact header names + Použít &kompaktní jména hlaviček + + + Indicates if compact header names should be used for headers that have a compact form. + Pokud je aktivováno, bude pro jména hlaviček použita krátká forma (pokud existuje). + + + Allow SDP change during call setup + Povolit SDP změny 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 "early media", např. "vyzváněcí tón". Pokud bude volání přijmuto, měl by poslat SIP UAS to samé SDP v odpovědí "200 OK". Po přijmutí SDP by měly být všechny následující SDP odpovědi ignorovány. Takto je to definováno v RFC 3261.</p> +<p>Pokud je povoleno, že se SDP během navázání hovoru může změnit, Twinkle neignoruje v následujících odpovědích SDP, nýbrž změní požadovaným způsobem vlastnosti RTP média streamu (např. kodek). 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ícera uživatelských profilů se stejným uživatelským SIP jménem ale s rozdílnými kontaktními adresami v doméně. Tyto profily lze potom použít současně. +</p> +<p> +Mnohé 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 téměř všemi SIP telefony. +</p> +<p> +<b>Používejte tuto volbu jen pokud ji skutečně potřebujete! Tedy pokud máte vícero profilů se stejným uživatelským jménem.</b></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 zvláštní hlavičce. + + + SIP extensions + SIP rozšíření + + + &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 100rel extension (PRACK):<br><br> +<b>deaktivováno</b>: 100rel rozšíření 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žádán. Požadavek je vložen do hlavičky "require header" v odchozím INVITE. Pokud protistrana pošle INVITE (=zavolá) a následně signalizuje, že je 100rel podporován, Twinkle si vyžádá PRACK při zasílání 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", kromě toho, že dojde k navázání hovoru i když protistrana 100rel nepodporuje. + +Toto nastavení ovlivňuje chování při "early media" (např. při znaku "volné linky"). + + + 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. Při tomto vám mohou vzniknout dodatečné finanční výdaje. + + + As&k user permission to transfer + Dotázat se uživatele na &povolení + + + 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í. Oproti pevné nebo GSM síti nepřebírá náklady hovoru ten, kdo volání přesměrovává, nýbrž ten kdo volání iniciuje. + + + Hold call &with referrer while setting up call to transfer target + T&winkle mezitím co zprostředkuje přesměrování, podrží hovor + + + Alt+W + + + + 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. Normálně by to měla provést zprostředkovávající protistrana. Viz následující volbu. Standardně je to vypnuto. + + + Ho&ld call with referee before sending REFER + Twink&le předtím než pošle signál REFER, podrží hovor jako zprostředkovatel + + + Alt+L + + + + Indicates if Twinkle should put the current call on hold when you transfer a call. + Pokud je aktivováno, Twinkle jako zprostředkovatel, předtím než pošle protistraně požadavek REFER o přesměrování, přepne stávající hovor do stavu podržení. Viz předchozí volba. +Standardně: aktivováno. + + + 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 + NAT traversal + + + &NAT traversal not needed + &NAT traversal 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. + Vybrat tuto volbu, pokud se mezi Twinkle a vaší SIP proxy nenachází žádný NAT (router). +Anebo pokud sice NAT existuje, ale je konfigurovaný tak, že potřebné porty jsou již na vás přesměrované. Popřípadě pokud váš SIP poskytovatel podporuje "hosted NAT traversal" (způsob jak VoIP poskytovatelé dokáží obejít problémy s NAT). + + + &Use statically configured public IP address inside SIP messages + V SIP telegramech použít &pevně nastavenou 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ř SIP zpráv (v SIP hlavičce a SDP obsahu) veřejnou IP adresu, namísto IP adresy vašeho síťového rozhraní. + +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. + Vybrat tuto volbu, pokud váš SIP poskytovatel nabízí STUN server k přemostění vaší NAT. + + + 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 (podle potřeby též včetně ":<portnr>", např. "stunsrv.cz:10000"). + + + &Public IP address: + Veřejná &adresa: + + + The public IP address of your NAT. + Veřejná adresa (IP, DynDNS-doména), pod kterou je váš NAT (realizovaný např. na vám nejbližším routeru) dosažitelný v internetu. Tato volba má smysl jen při neměnné adrese. + + + 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 zjistí 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 jako "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 jako "Telefonní číslo", které může mít v uživatelské části pouze číslice, *, #, + a zvláštní znaky (viz výše). V odchozím SIP zprávách označí Twinkle takové adresy parametrem "user=phone". +Pozor: Např. poskytovatel Sipgate měnil/mění výrazně své chování v mnohých funkcích, jakmile je tento parametr detekován. + + + &Remove special symbols from numerical dial strings + &Odstranit z vytáčecího řetězce 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 nebo přímým nakliknutím v adresáři, lze Twinkle zadat seznam nepřípustných znaků. Ty budou před samotným vytáčením automaticky z adresy odstraněny. + + + &Special symbols: + nepřípustné &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, napište zde zkušební telefonní číslo a stiskněte "Test". + + + &Test + &Otestovat + + + 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". + Pokud je detekováno příchozí volání, spustí se tento časovač. Pokud nebude volání do konce vypršení časové prodlevy přijato, vyšle Twinkle signál "480 User Not Responding" a hovor odmítne. + + + NAT &keep alive: + &STUN NAT-keep-alive: + + + &No answer: + "&Nedostupný" po: + + + Ring &back tone: + Tón &volné linky: + + + <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 signál volné linky v tomto uživatelském profilu.</p> + +<p>Tento tón nahrazuje tón volné linky 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 + Aktivovat ZRTP/SRTP &šifrování + + + 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 nezakódován. + + + ZRTP settings + ZRTP nastavení + + + O&nly encrypt audio if remote party indicated ZRTP support in SDP + &Zašifrovat jen pokud protistrana potvrdí podporu ZRTP + + + 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 schopná 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ého přenosu 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ý payload typ %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. (část vpravo od znaku @) +Často je shodné s doménovým jménem vašeho SIP poskytovatele. + +Pro přímé IP-to-IP spojení, tedy bez SIP poskytovatele, 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é jméno domény. + + + Invalid value for registrar. + Chybné jméno registrátora. + + + Invalid value for outbound proxy. + Chybné jméno outbound 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 + Výběr skriptu 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 + Výběr skriptu ke spuštění po selhání příchozího volání + + + Choose outgoing call script + Výběr skriptu ke spuštění při odchozím volání + + + Choose outgoing call answered script + Výběr skriptu ke spuštění při přijetí volání protistranou + + + Choose outgoing call failed script + Výběr skriptu ke spuštění při selhání odchozího volání + + + Choose local release script + Výběr skriptu ke spuštění při vlastním ukončení hovoru + + + Choose remote release script + Výběr skriptu ke spuštění při ukončení hovoru protistranou + + + Voice mail + Záznamník hovorů + + + &Follow codec preference from far end on incoming calls + Kodeky pro přenos hlasu při příchozím volání &vybere protistrana + + + <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 Twinkle 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 + Následovat &upřednostněné kodeky protistranou při odchozím volání + + + <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 zařazení 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 + Nahrazení + + + Indicates if the Replaces-extenstion is supported. + Pokud je aktivováno, podporuje Twinkle Replaces-Extension u metody PRACK. + + + Attended refer to AoR (Address of Record) + Přesměrování se zpětným dotazem použije "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. + Přesměrování se zpětným dotazem 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á. Může být tzv. "neroutovatelná". Přesměrované volání se pak nemusí dostat k cíli. +Alternativně může Twinkle použít AoR (Address of Record). Nevýhodou je, že při více uživatelských SIP účtech nejsou přihlášená koncová zařízení jednoznačně určená. Tzn. že přeměrovávací protistranou (vlastně SIP poskytovatelem) budou oslovena všechna koncová zařízení a budou signalizovat příchozí volání. + + + Privacy + Ochrana soukromých data + + + Privacy options + Nastavení ochrany soukromých dat + + + &Send P-Preferred-Identity header when hiding user identity + &Posílat "P-Preferred-Identity Header" při skrytí 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án při požadavku INVITE "P-Preferred-Identity Header". + + + <p> +You can customize the way Twinkle handles incoming calls. Twinkle can call a script when a call comes in. Based on the ouput 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 outputing 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> +Tento skript bude vyvolán, pokud bude detekován požadavek INVITE (volání). <br> +Prostudujte si prosím příručku v "/usr/share/doc/packages/twinkle/..." nebo podrobnou dokumentaci na "http://twinklephone.com" ! +</p> +<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í (default)<br> +<i>reject</i> - odmítnout volání<br> +<i>dnd</i> - odmítnout volání s poznámkou "do not disturb"<br> +<i>redirect</i> - přesměrovat volání na <tt>contact</tt> (viz tam)<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 záznamníku hovorů: + + + The SIP address or telephone number to access your voice mail. + SIP adresa nebo telefonní číslo, pod kterým je dosažitelný záznamník hovorů vašeho SIP poskytovatele. Někdy jsou dána dvě čísla. Jedno pro přístup z libovolného telefonu, např. "00420 211 58000111") a jedno SIP číslo, např. "50000". Potom by zde mělo být uvedeno SIP číslo. + + + Unsollicited + Asterisk režim + + + 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> +Pokud váš SIP poskytovatel nabízí "message waiting indication" = upozornění na uložené zprávy v záznamníku, může vás Twinkle informovat o nových i již vyslechnutých zprávách na vašem SIP záznamníku. V závisloti od vašeho poskytovatele, popř. jím používaným typem záznamníkové služby, je nutné zde nastavit jednu z náledujících metod přístupu: +</p> +<H3>Asterisk</H3> +<p> +Asterisk podporuje "unsollicited message waiting indication". +</p> +<H3>RFC 3842</H3> +<p> +"Sollicited message waiting indication" dle specifikace RFC 3842. +</p> + + + &MWI type: + &MWI Typ: + + + Sollicited MWI + RFC 3842 + + + Subscription &duration: + Doba &platnosti přihlášení: + + + Mailbox &user name: + Uživatelské jméno &mailboxu: + + + The hostname, domain name or IP address of your voice mailbox server. + Síťové jméno stanice, doménové jméno nebo IP adresa hlasového Voice-Mailbox serveru. Pokud vám váš poskytovatel k záznamníku neposkytl žádné údaje, zkuste jméno domény vašeho uživatelského účtu. + + + 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. + Ve specifikaci RFC 3842 MWI se koncové zařízení (Twinkle) hlásí na serveru k příjmu zpráv (SUBSCRIBE) na určitou dobu a před vypršením této doby by se přihlášení mělo znovu obnovit. Podobně jako "expiry time" / "doba platnosti" pro REGISTER. Viz SIP server. + + + Your user name for accessing your voice mailbox. + Uživatelské jméno k přístupu na váš Voice-Mailbox (záznamník hovorů). Pokud vám váš poskytovatel toto nesdělí, zkuste to s vaším SIP jménem. + + + Mailbox &server: + Mailbox &server: + + + Via outbound &proxy + Přes Outbound &proxy + + + Check this option if Twinkle should send SIP messages to the mailbox server via the outbound proxy. + Pokud je aktivováno, zasílá Twinkle SIP požadavky na Mailbox přes outbound proxy. + + + You must fill in a mailbox user name. + Musíte zadat uživatelské jméno pro Mailbox. + + + You must fill in a mailbox server + Musíte zadat Mailbox-Server + + + Invalid mailbox server. + Neplatné jméno pro Mailbox-Server. + + + Invalid mailbox user name. + Nepřípustné jméno Mailboxu. + + + Use domain &name to create a unique contact header value + Použít doménové &jméno pro vytvoření jednoznačné kontaktní hlavičky + + + Select ring back tone file. + Výběr souboru pro vyzváněcí tón u protistrany. + + + Select ring tone file. + Výběr souboru pro vyzváněcí tón. + + + Select script file. + Výběr souboru se skriptem. + + + %1 converts to %2 + %1 převést na %2 + + + Instant message + Instantní zpráva + + + Presence + Přítomnost + + + &Maximum number of sessions: + &Maximální počet seancí: + + + 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 seancí s instantními zprávami, nově příchozí seance budou odmítnuty. + + + Your presence + Vaše přítomnost + + + &Publish availability at startup + &Publikovat dostupnost při startu + + + Publish your availability at startup. + Publikovat vaši dostupnost při startu. + + + Buddy presence + Přítomnost buddyho + + + Publication &refresh interval (sec): + Interval &obnovení publikování (sec): + + + Refresh rate of presence publications. + Obnovovací frekvence publikování. + + + &Subscription refresh interval (sec): + &Interval obnovení přihlášení (sec): + + + Refresh rate of presence subscriptions. + Obnovovací frekvence přihlášení o přítomnosti. + + + Transport/NAT + Transport/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 + 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. + Transportní mód pro SIP. V auto módu je velikost zpráv určena tím, jaký transportní protokol je použit. Zprávy větší než UDP threshold jsou posílány přes TCP. Menší zprávy jsou posílány přes UDP. + + + T&ransport protocol: + T&ransportní protokol: + + + UDP t&hreshold: + UDP t&hreshold: + + + Messages larger than the threshold are sent via TCP. Smaller messages are sent via UDP. + Zprávy větší než threshold 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ů. Aplikací jsou vysílány ping pakety aby se testovalo, zda-li je spojení stále aktivní. + + + &Send composing indications when typing a message. + Při psaní zprávy vždy &vysílat příznaky editace. + + + Twinkle sends a composing indication when you type a message. This way the recipient can see that you are typing. + Twinkle vysílá při psaní zprávy příznak editace. Díky tomu je příjemce informován o tom, že je připravována nějaká zpráva. + + + 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 AKAv1-MD5 autentizaci. + + + 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í zpětnovazebnímu efektům. + + + 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 &hlasové aktivity + + + When enabled, voice activity detection detects whether the input signal represents a speech or a silence/background noise. + Pokud je aktivováno testuje funkce detekce hlasové aktivity zdali je vstupní signál hovor (hlas) nebo rušivé okolní zvuky. + + + &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 v reproduktorech může se šířit v místnosti 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ší dozvuk 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 straně volajícího. Nikoliv na vlastní straně. + + + Variable &bit-rate + Proměnná &vzorkovaci frekvence + + + 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. To znamená, že na úkor kvality je 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 vzorkovací frekvencí. Kódovací proces u tohoto kodeku je po většinu doby ří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 o tel-URI namísto 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. + Doménové jméno, IP adresa nebo jméno STUN serveru. + +Twinkle se pokouší pod touto doménou zjistit u DNS serveru potřebné údaje (RFC 2782). +Stačí proto u poskytovatelů internetové připojení, kteří toto podporují, uvést doménové jméno přihlašovacího 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 VoIP poskytovatelem. Je také první částí vaší kompletní SIP adresy <b>uzivatel</b>@domain.com U mnohých VoIp poskytovatelů to bývá označeno jako telefonní číslo. +<br><br> +*TYTO ÚDAJE JSOU POVINNÉ. + + + &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. + 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. +<p> +Prakticky kdekoliv v Twinkle lze pomocí klávesové kombinace <b>Shift-F1</b> nebo <b>pravým tlačítkem myši</b> vyvolat nápovědu k jednotlivým políčkům nebo tlačítkům. + + + &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é. Dostanete ho od vašeho VoIP poskytovatele. + + + 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éna nebo IP adresa, pod kterou je v internetu dostupný váš VoIP poskytovatel. Je to druhá část vaší úplné SIP adresy uzivatel@<b>domain.com</b>. U mnohých poskytovatelů je shodná s vlastní doménou poskytovatele. +Pro přímé spojení IP-to-IP (viz uživ. příručka) se zde uvede adresa (DynDNS nebo IP), pod kterou je <b>váš počítač</b> dostupný. +<br><br> +*TYTO ÚDAJE JSOU 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 nebo přezdívka. Tento údaj je jednou z částí, které jsou přenášeny při volání protistraně a eventuálně tam zobrazeny. Může být uvedeno cokoliv a není nutně vyžadováno. + + + 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 je shodné s adresou vašeho počítače je možné tento údaj vynechat. + + + &SIP service provider: + &SIP VoIP poskytovatel (Shift-F1 pro nápovědu): + + + &Password: + &Heslo: + + + &User name*: + &Uživatelské jméno *: + + + Your password for authentication. + Vaše přihlašovací heslo. Pokud necháte toto políčko prázdné, musíte heslo při každém přihlášení vyplnit do objevivšího se dialogového okna. + + + &OK + + + + Alt+O + + + + &Cancel + Zrušit (Es&c) + + + Alt+C + + + + None (direct IP to IP calls) + Žádné (přímé IP to IP volání) + + + 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 "@"). +Často je shodné s doménovým jménem vašeho SIP poskytovatele. + +Pro přímé spojení IP-to-IP, tedy bez SIP poskytovatele, je to doménové jméno nebo veřejná IP vašeho počítače. + + + Invalid value for SIP proxy. + Nepřípustná hodnota pro SIP proxy. + + + Invalid value for STUN server. + Nepřípustná hodnota pro STUN server. + + + + YesNoDialog + + &Yes + &Ano + + + &No + &Ne + + + diff --git a/src/gui/lang/twinkle_de.ts b/src/gui/lang/twinkle_de.ts new file mode 100644 index 0000000..5dd02ad --- /dev/null +++ b/src/gui/lang/twinkle_de.ts @@ -0,0 +1,6071 @@ + + + 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. + + + + 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 + + Twinkle - Diamondcard User Profile + + + + Your 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. + + + + &Account ID: + + + + &PIN code: + + + + &Your name: + + + + <p align="center"><u>Sign up for a Diamondcard account</u></p> + + + + &OK + + + + Alt+O + + + + &Cancel + Abbruch (Es&c) + + + Alt+C + + + + Fill in your account ID. + + + + Fill in your PIN code. + + + + A user profile with name %1 already exists. + + + + Your 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> + + + + + 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 succesfully 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: + + + + + 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 + + + + + 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. + + + + + 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 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 ouput 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 outputing 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 + + + diff --git a/src/gui/lang/twinkle_fr.ts b/src/gui/lang/twinkle_fr.ts new file mode 100644 index 0000000..2a58c53 --- /dev/null +++ b/src/gui/lang/twinkle_fr.ts @@ -0,0 +1,6038 @@ + + + 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. + + + + 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'adresse + + + Select an address from the address book. + Sélectionner une adresse du carnet d'adresse. + + + &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 + + Twinkle - Diamondcard User Profile + + + + Your 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. + 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. + + + &Account ID: + + + + &PIN code: + + + + &Your name: + &Votre nom: + + + <p align="center"><u>Sign up for a Diamondcard account</u></p> + + + + &OK + + + + Alt+O + + + + &Cancel + Annuler (Es&c) + + + Alt+C + + + + Fill in your account ID. + + + + Fill in your PIN code. + + + + A user profile with name %1 already exists. + + + + Your 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> + + + + + 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 succesfully 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'adresse &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'adresse local. + + + &Delete + &Supprimer + + + Alt+D + Alt+S + + + Delete a contact from the local address book. + Supprimer un contact du carnet d'adresse local. + + + &Edit + &Editer + + + Alt+E + + + + Edit a contact from the local address book. + Editer un contact du carnet d'adresse 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'adresse 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'adresse local de Twinkle.</p> + + + + 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: + + + + + 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'adresse + + + Select an address from the address book. + Sélectionner une adresse du carnet d'adresse. + + + 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'adresse + + + Select an address from the address book. + Sélectionner une adresse du carnet d'adresse. + + + &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'adresse + + + Select an address from the address book. + Sélectionner une adresse du carnet d'adresse. + + + 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 + + + + + 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'adresse + + + Select an address from the address book. + Sélectionner une adresse du carnet d'adresse. + + + &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 + + + + 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 - 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'adresse + + + Select an address from the address book. + Sélectionner une adresse du carnet d'adresse. + + + &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'adresse + + + 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'adresse. + + + 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'adresse 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'adresse. 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. + + + + + 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'adresse + + + Select an address from the address book. + Sélectionner une adresse du carnet d'adresse. + + + &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'adresse + + + Select an address from the address book. + Sélectionner une adresse du carnet d'adresse. + + + &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 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'adresse, 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'adresse 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 ouput 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 outputing 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 + + + diff --git a/src/gui/lang/twinkle_nl.ts b/src/gui/lang/twinkle_nl.ts new file mode 100644 index 0000000..e7281cd --- /dev/null +++ b/src/gui/lang/twinkle_nl.ts @@ -0,0 +1,6066 @@ + + + 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. + + + + 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 + + + Alt+O + + + + &Cancel + Ann&uleren + + + Alt+C + + + + 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 succesfully 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: + + + + 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 + + + + 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> + + + + 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. + + + + 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 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 ouput 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 outputing 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 + + + diff --git a/src/gui/lang/twinkle_ru.ts b/src/gui/lang/twinkle_ru.ts new file mode 100644 index 0000000..e71b60c --- /dev/null +++ b/src/gui/lang/twinkle_ru.ts @@ -0,0 +1,5729 @@ + + + + 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 + + + + &Cancel + &Отмена + + + Alt+C + + + + You must fill in a name. + Вы должны заполнить поле имя. + + + You must fill in a phone number or SIP address. + Вы должны заполнить поле номер телефона или SIP адрес + + + + 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 + &Записать + + + &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 + + + + 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 + + + + &Cancel + &Отмена + + + 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 + + Twinkle - Diamondcard User Profile + + + + Your 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. + + + + &Account ID: + + + + &PIN code: + + + + &Your name: + &Ваше имя: + + + <p align="center"><u>Sign up for a Diamondcard account</u></p> + + + + &OK + + + + Alt+O + + + + &Cancel + &Отмена + + + Alt+C + + + + Fill in your account ID. + + + + Fill in your PIN code. + + + + A user profile with name %1 already exists. + + + + Your 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> + + + + + DtmfForm + + Twinkle - DTMF + Twinkle - DTMF + + + Keypad + Цифровая клавиатура + + + 2 + + + + 3 + + + + Over decadic A. Normally not needed. + Не цифровое A. Обычно не нужно. + + + 4 + + + + 5 + + + + 6 + + + + Over decadic B. Normally not needed. + Не цифровое B. Обычно не нужно. + + + 7 + + + + 8 + + + + 9 + + + + Over decadic C. Normally not needed. + Не цифровое C. Обычно не нужно. + + + Star (*) + Звезда (*) + + + 0 + + + + Pound (#) + Решётка (#) + + + Over decadic D. Normally not needed. + Не цифровое D. Обычно не нужно. + + + 1 + + + + &Close + &Закрыть + + + Alt+C + + + + + FreeDeskSysTray + + Show/Hide + Показать/Скрыть + + + Quit + Выход + + + + 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 + + + + 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. + Не могу найти сетевой интерфейс. 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 succesfully 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. + + + + 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 + + + + Cannot open web browser: %1 + + + + Configure your web browser in the system settings. + + + + + GetAddressForm + + Twinkle - Select address + Twinkle - Выбор адреса + + + &KAddressBook + Адресная книга &KDE + + + Name + Имя + + + Type + Тип + + + Phone + Телефон + + + 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 + + + + 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 + + + + Reload the list of addresses from KAddressbook. + Перезагрузить список адресов из Адресной книги KDE. + + + &Local address book + &Локальная адресная книга + + + Remark + Описание + + + Contacts in the local address book of Twinkle. + Контакты из локальной адресной книги 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> + <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 + + + + 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> + <p>Очистить всю историю звонков.</p> +<p><b>Заметка:</b> очищает <b>все</b> записи, записи удаляются безвозратно, и вы никогда не увидите их в любых вариантах просмотра.</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: + + + + + 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 + + + + <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 + + + + + LogViewForm + + Twinkle - Log + Twinkle - Журнал + + + Contents of the current log file (~/.twinkle/twinkle.log) + Содержимое текущего журнала (~/.twinkle/twinkle.log) + + + &Close + &Закрыть + + + Alt+C + + + + C&lear + О&чистить + + + 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 + Общение + + + The exchanged messages. + Сообщения, которыми вы обменялись. + + + Type your message here and then press "send" to send it. + Введите ваше сообщение и нажмите "Отправить" для его отсылки. + + + &Send + &Отправить + + + Alt+S + + + + Send the message. + Отправить сообщение. + + + Delivery failure + Ошибка доставки + + + Delivery notification + Сообщение доставки + + + Instant message toolbar + Панель мгновенных сообщений + + + 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 + + + + 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. + Адрес куда вы хотите позвонить. Это может быть полный 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 + + + + Click to switch to line 1. + Нажмите для переключения к линии 1. + + + From: + Откуда: + + + To: + Куда: + + + Subject: + Тема: + + + Visual indication of line state. + Наглядное отображение состояния линии. + + + idle + No need to translate + простой + + + Call is on hold + Звонок на удержании + + + Voice is muted + Микрофон заглушен + + + Conference call + Конференц связь + + + Transferring call + Передача звонка + + + <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> + + + + 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: + Линия &2: + + + Alt+2 + + + + Click to switch to line 2. + Нажмите для переключения к линии 2. + + + &File + &Файл + + + &Edit + &Редактировать + + + C&all + З&вонить + + + Activate line + Активировать линию + + + &Message + &Чат + + + &Registration + &Регистрация + + + &Services + &Сервисы + + + &View + &Показать + + + &Help + &Справка + + + Call Toolbar + Панель звонков + + + Quit + Выход + + + &Quit + &Выход + + + Ctrl+Q + + + + About Twinkle + О программе Twinkle + + + &About Twinkle + &О программе Twinkle + + + Call + toolbar text + Звон + + + &Call... + call menu text + &Звонить... + + + Call someone + Позвонить кому либо + + + F5 + + + + Answer + toolbar text + Ответ + + + &Answer + menu text + &Ответить + + + Answer incoming call + Ответить входящий звонок + + + F6 + + + + Bye + toolbar text + Заверш + + + &Bye + menu text + &Завершить + + + Release call + Завершить звонок + + + Esc + + + + Reject + toolbar text + Отклон + + + &Reject + menu text + &Отклонить + + + Reject incoming call + Отклонить входящий звонок + + + F8 + + + + Hold + toolbar text + Удерж + + + &Hold + menu text + &Удержать + + + Put a call on hold, or retrieve a held call + Поставить звонок на удержание, или ответить второй вызов + + + Redirect + toolbar text + Направ + + + R&edirect... + menu text + П&еренаправить... + + + Redirect incoming call without answering + Перенаправить входящий звонок без ответа + + + Dtmf + toolbar text + + + + &Dtmf... + menu text + + + + Open keypad to enter digits for voice menu's + Открывает панель цифровых клавиш для использования в голосовых меню + + + Register + Регистрация + + + &Register + &Регистрация + + + Deregister + Разрегистрация + + + &Deregister + &Разрегистрация + + + Deregister this device + Разрегистрация этого устройства + + + Show registrations + Показать регистрации + + + &Show registrations + &Показать регистрации + + + Terminal capabilities + Ограничения терминала + + + &Terminal capabilities... + menu text + &Ограничения терминала... + + + Request terminal capabilities from someone + Запрос органичений терминала от кого-либо + + + Do not disturb + Не беспокоить + + + &Do not disturb + &Не беспокоить + + + Call redirection + Перенаправление звонка + + + Call &redirection... + &Перенаправление звонка... + + + Redial + toolbar text + Повт + + + &Redial + menu text + &Повторить + + + Repeat last call + Повторить последний звонок + + + F12 + + + + About Qt + О Qt + + + About &Qt + О &Qt + + + User profile + Профиль пользователя + + + &User profile... + &Профиль пользователя... + + + Conf + toolbar text + Конф + + + &Conference + menu text + &Конференция + + + Join two calls in a 3-way conference + Объединить два звонка в единую 3-х строннюю конференцию + + + Mute + toolbar text + Вык.Зв + + + &Mute + menu text + &Выключить звук + + + Mute a call + Отключить микрофон + + + Xfer + toolbar text + Перекл + + + Trans&fer... + menu text + Пере&ключить... + + + 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 + Линия 1 + + + Line 2 + Линия 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. + Нажмите для подтверждения 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 + + + + 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 + + + + Diamondcard + + + + Manual + + + + &Manual + + + + Sign up + + + + &Sign up... + + + + Recharge... + + + + Balance history... + + + + Call history... + + + + Admin center... + + + + Recharge + + + + Balance history + + + + Admin center + + + + + 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 + + + + Alt+O + + + + &Cancel + &Отмена + + + 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 + + + + F12 + + + + 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 + + + + 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 + + + + Make the selected network interface the default interface. The next time you start Twinkle, this interface will be automatically selected. + Сделать выбранный сетевой интерфейс интерфейсом по умолчанию. В следующий раз когда Twinkle будет запускатся, этот интерфейс будет выбран автоматически. + + + &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 + Twinkle - Выбор пользовательского профиля + + + Select user profile(s) to run: + Выберите пользовательский профиль(ли) для запуска: + + + User profile + Профиль пользователя + + + Tick the check boxes of the user profiles that you want to run and press run. + Поставьте отметки на профилях которые вы хотите запустить и нажмите запуск. + + + &New + &Новый + + + 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. + Сделать выбранный профиль как профиль по умолчанию. При следующих запусках Twinkle, этот профиль будет автоматически запущен. + + + &Run + &Запуск + + + Alt+R + + + + Run Twinkle with the selected profiles. + Запустить Twinkle с выбраным профилем. + + + 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> + <html>Перед тем как использовать Twinkle, вы должны создать пользовательский профиль.<br>Нажмите OK Для создания профиля.</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>Для создания нового профиля вы можете использовать редактор профилей. В редакторе профилей вы можете гибко изменить настройки для SIP протокола, RTP и множество других тонких настроек.<br><br>Как альтернативу вы можете использовать мастер для быстрой настройки. Мастер спросит у вас только самые необходимые настройки. Если вы создаёте профиль мастером, то позже сможете редактировать его редактором профилей.<br><br>Выберите метод который вы будете использовать.</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 + + + + 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 - Выбор пользователя + + + &Cancel + &Отмена + + + Alt+C + + + + &Select all + &Выбрать все + + + Alt+S + + + + &OK + &Принять + + + Alt+O + + + + C&lear all + О&чистить все + + + Alt+L + + + + purpose + No need to translate + + + + User + Пользователь + + + 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 + + + + &Cancel + &Отмена + + + 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> + + + + &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. + Вы можете определить до 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 + + + + 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 + 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 + + + + 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. + + + + Reduce &noise from the microphone + Подавлять &эхо от микрофона + + + Advanced + Дополнительно + + + OSS &fragment size: + 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. + + + + 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 + + + + Indicates if reports marked as "debug" will be logged. + Указывает будут ли записываться отладочные сообщения в журнал. + + + Log &SIP reports + Записывать &SIP отчёты + + + Alt+S + + + + Indicates if SIP messages will be logged. + Указывает будут ли записываться SIP сообщения в журнал. + + + Log S&TUN reports + Записывать S&TUN отчёты + + + Alt+T + + + + Indicates if STUN messages will be logged. + Указывает будут ли записываться STUN сообщения в журнал. + + + 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. + Включите эту опцию если вы хотите видеть значок Twinkle в системном лотке. Значок в системном лотке создаётся при старте 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. + Включите эту опцию если вы хотите чтобы 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 будет постоянно прятаться в системном лотке. Это удобно когда вы выбрали профиль пользователя по умолчанию. + + + Default user profiles + Стандартный профиль пользователя + + + 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 + + + + 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 + + + + 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 + + + + 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 + + + + 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. + Определите имя файла из *.wav файлов, который будет проигрываться как вызывной сигнал. + + + 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. + Определите файл из .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 + + + + 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. + Искать фото звонящего в вашей адресной книге и отобразить его при входящем звонке. + + + none + This is the 'none' in default IP address combo + нету + + + none + This is the 'none' in default network interface combo + нету + + + 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. + + + + + 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 + + + + + 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. + Выберите адрес из адресной книги. + + + Type of transfer + Тип переключения + + + &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 . + Ошибка создания файла журнала: %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 . + + + Lock file %1 already exist, but cannot be opened. + Файл блокировки %1 уже существует и не может быть открыт. + + + %1 is already running. +Lock file %2 already exists. + %1 уже запушен. +файл блокировки %2 уже существует. + + + Cannot create %1 . + Не могу создать %1 . + + + Cannot write to %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. + + + + 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 + + + + 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 . + + + + + 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 + + + + Accept and save your changes. + Принять и сохранить изменения. + + + &Cancel + &Отмена + + + 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 + + + + 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 + + + + 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.) + Если вы отметите эту опцию 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 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. + Приоритетный payload размер для G.711 и G.726 кодеков. + + + 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: + i&LBC payload тип: + + + iLBC &payload size (ms): + + + + The dynamic type value (96 or higher) to be used for iLBC. + + + + 20 + + + + 30 + + + + The preferred payload size for iLBC. + Приоритетный payload размер для 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 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: + DTMF ур&овень: + + + The power level of the DTMF tone in dB. + + + + The pause after a DTMF tone. + Пауза после DTMF тона. + + + DTMF &duration: + DTMF &длинна: + + + DTMF payload &type: + + + + DTMF &pause: + DTMF &паузы: + + + dB + дБ + + + Duration of a DTMF tone. + Продолжительность посылки DTMF тона. + + + DTMF t&ransport: + 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> + + + + 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: + S&TUN сервер: + + + The hostname, domain name or IP address of the STUN server. + Имя сервера, доменное имя или IP адрес STUN сервера. + + + &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 ouput 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 outputing 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 + + + + 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. + 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: + + + + Sollicited 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. + + + + 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 + + + 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 + байт + + + 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. + + + + Use tel-URI for telephone &number + Использовать tel-URI для телефонного &номера + + + Expand a dialed telephone number to a tel-URI instead of a sip-URI. + + + + &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 посылает оповещения при наборе сообщения. Получатель будет видеть когда вы печатате. + + + 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 - Мастер + + + 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>Other</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 + + + + &Cancel + &Отмена + + + 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 + &Нет + + + diff --git a/src/gui/lang/twinkle_sv.ts b/src/gui/lang/twinkle_sv.ts new file mode 100644 index 0000000..a63d54d --- /dev/null +++ b/src/gui/lang/twinkle_sv.ts @@ -0,0 +1,5772 @@ + + + + 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. + + + + 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 + + Twinkle - Diamondcard User Profile + + + + Your 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. + 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. + + + &Account ID: + + + + &PIN code: + + + + &Your name: + &Ditt namn: + + + <p align="center"><u>Sign up for a Diamondcard account</u></p> + + + + &OK + &OK + + + Alt+O + + + + &Cancel + &Avbryt + + + Alt+C + + + + Fill in your account ID. + + + + Fill in your PIN code. + + + + A user profile with name %1 already exists. + + + + Your 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> + + + + + 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 succesfully 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 + + + Remark + + + + 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: + + + + + 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: + + + Visual indication of line state. + + + + idle + No need to translate + Overksam + + + Call is on hold + + + + Voice is muted + + + + Conference call + Konferenssamtal + + + Transferring call + + + + <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> + + + + 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 + + + Call Toolbar + + + + 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 + toolbar text + + + + &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 + + + + + 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. + + + + + 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 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 ouput 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 outputing 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 + + + diff --git a/src/gui/lang/twinkle_xx.ts b/src/gui/lang/twinkle_xx.ts new file mode 100644 index 0000000..6f2ade3 --- /dev/null +++ b/src/gui/lang/twinkle_xx.ts @@ -0,0 +1,5669 @@ + + + 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. + + + + + 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 + + Twinkle - Diamondcard User Profile + + + + Your 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. + + + + &Account ID: + + + + &PIN code: + + + + &Your name: + + + + <p align="center"><u>Sign up for a Diamondcard account</u></p> + + + + &OK + + + + Alt+O + + + + &Cancel + + + + Alt+C + + + + Fill in your account ID. + + + + Fill in your PIN code. + + + + A user profile with name %1 already exists. + + + + Your 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> + + + + + 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 + + + + + FreeDeskSysTray + + Show/Hide + + + + Quit + + + + + 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 succesfully 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 + + + + Name + + + + Type + + + + Phone + + + + 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 + + + + Remark + + + + 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: + + + + + 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 + + + + Instant message toolbar + + + + 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: + + + + Visual indication of line state. + + + + idle + No need to translate + + + + Call is on hold + + + + Voice is muted + + + + Conference call + + + + Transferring call + + + + <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> + + + + 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 + + + + Call Toolbar + + + + Quit + + + + &Quit + + + + Ctrl+Q + + + + About Twinkle + + + + &About Twinkle + + + + Call + toolbar text + + + + &Call... + call menu text + + + + Call someone + + + + F5 + + + + Answer + toolbar text + + + + &Answer + menu text + + + + Answer incoming call + + + + F6 + + + + Bye + toolbar text + + + + &Bye + menu text + + + + Release call + + + + Esc + + + + Reject + toolbar text + + + + &Reject + menu text + + + + Reject incoming call + + + + F8 + + + + Hold + toolbar text + + + + &Hold + menu text + + + + Put a call on hold, or retrieve a held call + + + + Redirect + toolbar text + + + + R&edirect... + menu text + + + + Redirect incoming call without answering + + + + Dtmf + toolbar text + + + + &Dtmf... + menu text + + + + Open keypad to enter digits for voice menu's + + + + Register + + + + &Register + + + + Deregister + + + + &Deregister + + + + Deregister this device + + + + Show registrations + + + + &Show registrations + + + + Terminal capabilities + + + + &Terminal capabilities... + menu text + + + + Request terminal capabilities from someone + + + + Do not disturb + + + + &Do not disturb + + + + Call redirection + + + + Call &redirection... + + + + Redial + toolbar text + + + + &Redial + menu text + + + + Repeat last call + + + + F12 + + + + About Qt + + + + About &Qt + + + + User profile + + + + &User profile... + + + + Conf + toolbar text + + + + &Conference + menu text + + + + Join two calls in a 3-way conference + + + + Mute + toolbar text + + + + &Mute + menu text + + + + Mute a call + + + + Xfer + toolbar text + + + + Trans&fer... + menu text + + + + 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 + + + + + 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: + + + + User profile + + + + 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 + + + + User + + + + 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. + + + + Default user profiles + + + + 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. + + + + + 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. + + + + Type of transfer + + + + &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 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 ouput 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 outputing 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 + + + + diff --git a/src/gui/logviewform.ui b/src/gui/logviewform.ui new file mode 100644 index 0000000..c99d24c --- /dev/null +++ b/src/gui/logviewform.ui @@ -0,0 +1,123 @@ + +LogViewForm + + + LogViewForm + + + + 0 + 0 + 599 + 472 + + + + Twinkle - Log + + + + unnamed + + + + logTextEdit + + + PlainText + + + NoWrap + + + true + + + Contents of the current log file (~/.twinkle/twinkle.log) + + + + + closePushButton + + + &Close + + + Alt+C + + + + + spacer23 + + + Horizontal + + + Expanding + + + + 360 + 20 + + + + + + clearPushButton + + + C&lear + + + Alt+L + + + Clear the log window. This does <b>not</b> clear the log file itself. + + + + + + + closePushButton + clicked() + LogViewForm + close() + + + clearPushButton + clicked() + LogViewForm + clear() + + + + logTextEdit + clearPushButton + closePushButton + + + qfile.h + qtextstream.h + log.h + qdialog.h + qstring.h + audits/memman.h + logviewform.ui.h + + + QFile *logfile; + QTextStream *logstream; + + + show() + closeEvent( QCloseEvent * ev ) + update( bool log_zapped ) + clear() + + + + diff --git a/src/gui/logviewform.ui.h b/src/gui/logviewform.ui.h new file mode 100644 index 0000000..859de0d --- /dev/null +++ b/src/gui/logviewform.ui.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +void LogViewForm::show() +{ + if (isShown()) { + raise(); + return; + } + + QString fname = log_file->get_filename().c_str(); + logfile = new QFile(fname); + MEMMAN_NEW(logfile); + logstream = NULL; + if (logfile->open(IO_ReadOnly)) { + logstream = new QTextStream(logfile); + MEMMAN_NEW(logstream); + logTextEdit->setText(logstream->read()); + + // Set cursor position at the end of text + logTextEdit->scrollToBottom(); + } + + log_file->enable_inform_user(true); + + QDialog::show(); + raise(); +} + +void LogViewForm::closeEvent( QCloseEvent *ev ) +{ + log_file->enable_inform_user(false); + logTextEdit->clear(); + + 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 (!isShown()) return; + + if (log_zapped) { + close(); + show(); + return; + } + + if (logstream) { + QString s = logstream->read(); + if (!s.isNull() && !s.isEmpty()) { + logTextEdit->append(s); + } + } +} + +void LogViewForm::clear() +{ + logTextEdit->clear(); +} + diff --git a/src/gui/main.cpp b/src/gui/main.cpp new file mode 100644 index 0000000..36242f9 --- /dev/null +++ b/src/gui/main.cpp @@ -0,0 +1,1201 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "twinkle_config.h" + +#ifdef HAVE_KDE +#include +#include +#endif + +#include +#include +#include +#include +#include + +#include "mphoneform.h" + +#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" + +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; + + +/** + * 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 conjuction 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 sytem 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; + 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.ascii()); + } + } 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()).ascii(); + } else { + msg = qApp->translate("GUI", "Failed to create a %1 socket (SIP) on port %2") + .arg(sock_type) + .arg(sys_config->get_sip_port()).ascii(); + } + 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 *qtranslator) { + 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. + QMimeSourceFactory *factory_qt = QMimeSourceFactory::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 + QMimeSourceFactory *factory_kde = QMimeSourceFactory::takeDefaultFactory(); + + // Make the Qt factory the default to make the embedded icons work. + QMimeSourceFactory::setDefaultFactory(factory_qt); + + // Add the KDE factory + QMimeSourceFactory::addFactory(factory_kde); +#else + int tmp = argc; + qa = new t_twinkle_application(tmp, argv); + MEMMAN_NEW(qa); +#endif + QTextCodec::setCodecForCStrings(QTextCodec::codecForName("utf8")); + QTextCodec::setCodecForTr(QTextCodec::codecForName("utf8")); + + // Install Qt translator + // Do not report to memman as the translator will be deleted + // automatically when the QApplication is deleted. + qtranslator = new QTranslator(0); + qtranslator->load(QString("twinkle_") + QTextCodec::locale(), + QString(sys_config->get_dir_lang().c_str())); + qa->installTranslator(qtranslator); + + // 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; +} + +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(); + + QApplication *qa = NULL; + QTranslator *qtranslator = NULL; + + // 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.ascii(), true); + // Do not exit now as this option may be used in conjuction + // 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.ascii(), + 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.ascii(), + 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, qtranslator); + 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?").ascii(); + } + 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.ascii(), "::main"); + + if (sys_config->get_ui_session_id() == qa->sessionId().ascii()) { + 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.ascii()); + } + } + + 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()).ascii(); + } else { + error_msg = qApp->translate("GUI", "The following profiles are both for user %1").arg(user_config.get_name().c_str()).ascii(); + } + 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.").ascii(); + error_msg += "\n"; + error_msg += QString("If these are users for different domains, then enable the following option in your user profile (SIP protocol):"); + error_msg += "\n"; + error_msg += QString("Use domain name to create a unique contact header"); + } else { + error_msg += qApp->translate("GUI", "You can only run multiple profiles for different users.").ascii(); + 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)"); + error_msg += ":\n"; + error_msg += qApp->translate("GUI", "Use domain name to create a unique contact header"); + } + 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); + } + + // 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; + 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 (...) { + 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 (qtranslator) { + MEMMAN_DELETE(qtranslator); + delete(qtranslator); + } + + 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; +} diff --git a/src/gui/messageform.ui b/src/gui/messageform.ui new file mode 100644 index 0000000..ee900e1 --- /dev/null +++ b/src/gui/messageform.ui @@ -0,0 +1,325 @@ + +MessageForm + + + MessageForm + + + + 0 + 0 + 578 + 356 + + + + Twinkle - Instant message + + + + unnamed + + + + layout154 + + + + unnamed + + + + toTextLabel + + + &To: + + + toLineEdit + + + + + fromComboBox + + + The user that will send the message. + + + + + layout152 + + + + unnamed + + + + toLineEdit + + + 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. + + + + + addressToolButton + + + TabFocus + + + + + + F10 + + + kontact_contacts.png + + + Address book + + + Select an address from the address book. + + + + + + + profileTextLabel + + + &User profile: + + + fromComboBox + + + + + + + conversationGroupBox + + + Conversation + + + + unnamed + + + + conversationBrowser + + + + + + + layout158 + + + + unnamed + + + + msgLineEdit + + + Type your message here and then press "send" to send it. + + + + + sendPushButton + + + &Send + + + Alt+S + + + false + + + true + + + Send the message. + + + + + + + + + + Toolbar + + + false + + + Instant message toolbar + + + + + + + TextBrowserNoAutoLink +
textbrowsernoautolink.h
+ + -1 + -1 + + 0 + + 7 + 7 + 0 + 0 + + image0 + linkClicked( const QString & link ) +
+
+ + + + sendFileAction + + + attach.png + + + Send file... + + + Send file + + + + + + 89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b000003b149444154388dad945f4c5b551cc73fe7dc4b7b4bcba0762d45c43114323599ee6192609c51d883892ce083f1718b3ebb185f8dc91e972cf39d2d2a2f1af664b6f1e0fe3863a0718969700eb0c52142da0242a1bd6d696f7bcff101585203ceb8fd9ece39f99dcff9fe7edf939f88c562ec465f5f9fe609442c161362173c3e3eae7b7a7ac8e7f36432196cdbfe4f907c3e4f2291201e8fe338cec3737357e9e8e828aded1e229d650e1f2d51754b082110124c13a4dc5ea341eb9dc284c0558a853f3ce8cb0677ef500fde7d39d2596679e326597b8e9abb85d7a770ab16ab6983ec5a05b487a70e36f0f4e10afe408d6a558310980108478dba4a1e8233990c5d474b64ed39aa3a8fe5f3317fbf81dbd70bccfeb205947632fd74f6589c1c6ea2f70d03a58ba0c1f2c9bdc1b66de3b8256a6e11cbe7e3ee1d181b590124fe2693aeee08d223c82c3a2c24b7b874bec8f26288774f7bd054504aef0dde6e99c0eb83f9fb266323cb80a27fb0958141836044605a2ee5523393371cc646fee2da37195aa35d0c0c5b4859ac03d7e91712dcaac5adab3650a3ff9d08ef7dd8404bb48869e5d958b5b87dadc4c9a1464e9f0d0326df7ebd86bd2e310cb1bf62d384d59441f2d70a070e1c60e09489929b988681bdd9cc97170bcc4c65595f71f8e0e3301337fc24a7732467831875a47f289652b0be5e4151e6d07316c1b0c0340d8ab92023e76d66a6b2840e36d2fb7a13fee632475e6edc367ea98a90fb98b7dd6310ca0328a44761582e1bab41befabcc0ec940d28bc5e93b68e064cab84e1d9beaeb48934eac1f53b01c1b000fca496aa54b61a99fcde61662a4b4b4b23d1680be9d426173e4df3602a48ea411989a4fd590f52a8fd156b05ed9d350e3defe3cfdf4b4c7ce770ea7d3fb9f520afbe1620daeee5c26735d20b9b9cfb6811a754a439e4e5c5639a4caa1e5caf586bfc0197b78702005cb9b4cae4cd3267ce8638fe964bd72b393e39d74928d242617303a756a37f284447770dcdbffc6384a05a85de1306e9a52057c7527c7131c3c42d3f475eb2303c82d4fc3276d6811db37efeb148723082d9b08f79f97c1e5729109a9a28307cc622d2d6cdf52b2b24efe548dedb00142009862cfa879ee1a71f6cec928353511472fbf4389148b0b0e0c108081412458dfe21c9f11351e67e7358595468246d1d1e5e38a6e9e851bc39d84ab502a669331dafec0d8ec7e3e8cb06e1a881d727d1ae40180a434a8c9db129a54126ad48a7358c2b4c5352c8c374bcccdab2bb37d8719cba79fab8211f9df218e0582c261e95f8bfc04f1a1e8bc5c4dfe0a190172af6a9690000000049454e44ae426082 + + + + + sendPushButton + clicked() + MessageForm + sendMessage() + + + addressToolButton + clicked() + MessageForm + showAddressBook() + + + conversationBrowser + linkClicked(const QString&) + MessageForm + showAttachmentPopupMenu(const QString&) + + + sendFileAction + activated() + MessageForm + chooseFileToSend() + + + toLineEdit + textChanged(const QString&) + MessageForm + toAddressChanged(const QString&) + + + msgLineEdit + textChanged(const QString&) + MessageForm + showMessageSize() + + + + getaddressform.h + qstring.h + user.h + im/msg_session.h + phone.h + qpopupmenu.h + qlabel.h + gui.h + sockets/url.h + qstylesheet.h + audits/memman.h + util.h + qpixmap.h + qcursor.h + qfiledialog.h + utils/file_utils.h + qfile.h + sendfileform.h + qstatusbar.h + qframe.h + messageform.ui.h + + + extern t_phone *phone; + + + QDialog *_saveAsDialog; + map<string, string> _filenameMap; + im::t_msg_session *_msgSession; + bool _remotePartyComplete; + GetAddressForm *_getAddressForm; + QPopupMenu *attachmentPopupMenu; + QString clickedAttachment; + void *_serviceMap; + QLabel *_isComposingLabel; + QLabel *_msgSizeLabel; + + + closeEvent( QCloseEvent * e ) + show() + selectUserConfig( t_user * user_config ) + showAddressBook() + selectedAddress( const QString & address ) + sendMessage() + sendFile( const QString & filename, const QString & subject ) + addMessage( const im::t_msg & msg, const QString & name ) + displayError( const QString & errorMsg ) + displayDeliveryNotification( const QString & notification ) + setRemotePartyCaption( void ) + showAttachmentPopupMenu( const QString & attachment ) + attachmentPopupActivated( int id ) + saveAttachment() + chooseFileToSend() + setComposingIndication( const QString & name ) + clearComposingIndication() + setLocalComposingIndicationActive() + keyPressEvent( QKeyEvent * e ) + toAddressChanged( const QString & address ) + showMessageSize() + + + init() + destroy() + updateMessageSession() + prepareSendMessage() + + + + + textbrowsernoautolink.h + +
diff --git a/src/gui/messageform.ui.h b/src/gui/messageform.ui.h new file mode 100644 index 0000000..f44fc02 --- /dev/null +++ b/src/gui/messageform.ui.h @@ -0,0 +1,616 @@ +/**************************************************************************** +** 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifdef HAVE_KDE +#include +#include +#include +#include +#include +#else +#include "utils/mime_database.h" +#endif + +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) ) ) + +void MessageForm::init() +{ + setWFlags(getWFlags() | Qt::WDestructiveClose); + + _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 QPopupMenu(this); + MEMMAN_NEW(attachmentPopupMenu); + + connect(attachmentPopupMenu, SIGNAL(activated(int)), + this, SLOT(attachmentPopupActivated(int))); + + _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->text(i) == + user_config->get_profile_name().c_str()) + { + fromComboBox->setCurrentItem(i); + break; + } + } +} + +void MessageForm::showAddressBook() +{ + if (!_getAddressForm) { + _getAddressForm = new GetAddressForm( + this, "select address", 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().ascii()); + if (!from_user) { + // The user profile is not active anymore + fromComboBox->setFocus(); + return false; + } + + ui->expand_destination(from_user, toLineEdit->text().stripWhiteSpace().ascii(), + 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().ascii(), 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.ascii()); + if (!mime_type.empty()) { + media = t_media(mime_type); + } +#endif + + _msgSession->send_file(filename.ascii(), media, subject.ascii()); +} + +/** + * 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(); + s += QStyleSheet::escape(name); + 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 { + s += QStyleSheet::escape(msg.message.c_str()); + } + } + + // 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").ascii(); + s += ": "; + s += QStyleSheet::escape(errorMsg); + s += ""; + + conversationBrowser->append(s); +} + +void MessageForm::displayDeliveryNotification(const QString ¬ification) +{ + QString s = ""; + s += ""; + s += tr("Delivery notification").ascii(); + s += ": "; + s += QStyleSheet::escape(notification); + 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(); + setCaption(ui->format_sip_address(user, + remote_party.display, remote_party.url).c_str()); +} + +void MessageForm::showAttachmentPopupMenu(const QString &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; + + attachmentPopupMenu->clear(); + + QIconSet saveIcon(QPixmap::fromMimeSource("save_as.png")); + attachmentPopupMenu->insertItem(saveIcon, "Save as...", 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); + QIconSet iconSet; + iconSet.setPixmap(pixmap, QIconSet::Small); + attachmentPopupMenu->insertItem(iconSet, menuText, id++); + } + + QIconSet openIcon(QPixmap::fromMimeSource("fileopen.png")); + attachmentPopupMenu->insertItem(openIcon, tr("Open with..."), id++); +#endif + + attachmentPopupMenu->popup(QCursor::pos()); +} + +void MessageForm::attachmentPopupActivated(int id) { +#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(QString::null, QString::null, this, 0, true); + MEMMAN_NEW(d); + d->setMode(QFileDialog::AnyFile); + + connect(d, SIGNAL(fileSelected(const QString &)), this, + SLOT(saveAttachment())); +#endif + d->setSelection(_filenameMap[clickedAttachment.ascii()].c_str()); + d->setCaption(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 + QString filename = d->selectedFile(); + + if (QFile::exists(filename)) { + bool overwrite = ((t_gui *)ui)->cb_ask_msg(this, + tr("File already exists. Do you want to overwrite this file?").ascii(), + MSG_WARNING); + + if (!overwrite) return; + } + + if (!filecopy(clickedAttachment.ascii(), filename.ascii())) { + ((t_gui *)ui)->cb_show_msg(this, tr("Failed to save attachment.").ascii(), + MSG_CRITICAL); + } +} + +/** Choose a file to send */ +void MessageForm::chooseFileToSend() +{ + // Indicate that a message is being composed. + setLocalComposingIndicationActive(); + + SendFileForm *form = new SendFileForm(); + 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()->clear(); + + 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); +} diff --git a/src/gui/messageformview.cpp b/src/gui/messageformview.cpp new file mode 100644 index 0000000..b869520 --- /dev/null +++ b/src/gui/messageformview.cpp @@ -0,0 +1,159 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} + +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(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::subject_destroyed() +{ + _msgSession = NULL; + + if (!_destructing) close(); +} + +void MessageFormView::show() +{ + ((t_gui *)ui)->fill_user_combo(fromComboBox); + updatePartyInfo(); + MessageForm::show(); +} diff --git a/src/gui/messageformview.h b/src/gui/messageformview.h new file mode 100644 index 0000000..951cb51 --- /dev/null +++ b/src/gui/messageformview.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 +{ +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); +}; + +#endif diff --git a/src/gui/mphoneform.ui b/src/gui/mphoneform.ui new file mode 100644 index 0000000..50f65be --- /dev/null +++ b/src/gui/mphoneform.ui @@ -0,0 +1,2851 @@ + +MphoneForm + + + MphoneForm + + + true + + + + 0 + 0 + 739 + 693 + + + + + 5 + 5 + 0 + 0 + + + + + 714 + 693 + + + + Twinkle + + + twinkle16.png + + + false + + + false + + + true + + + + unnamed + + + + splitter2 + + + Horizontal + + + + + Buddy list + + + false + + + false + + + + buddyListView + + + + 7 + 7 + 150 + 0 + + + + + 0 + 0 + + + + true + + + LastColumn + + + 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. + + + + + layout13 + + + + unnamed + + + + layout76 + + + + unnamed + + + + callTextLabel + + + &Call: + Label in front of combobox to enter address + + + callComboBox + + + + + layout71 + + + + unnamed + + + + callComboBox + + + + 3 + 0 + 0 + 0 + + + + true + + + 10 + + + NoInsertion + + + 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. + + + + + addressToolButton + + + TabFocus + + + + + + F10 + + + kontact_contacts.png + + + Address book + + + Select an address from the address book. + + + + + callPushButton + + + Dial + + + true + + + Dial the address. + + + + + + + userLabel + + + &User: + + + userComboBox + + + + + layout75 + + + + unnamed + + + + userComboBox + + + + 3 + 0 + 0 + 0 + + + + The user that will make the call. + + + + + layout74 + + + + unnamed + + + + statAaLabel + + + + 0 + 0 + 0 + 0 + + + + 13 + + + Plain + + + + + + auto_answer.png + + + Auto answer indication. + + + + + statCfLabel + + + + 0 + 0 + 0 + 0 + + + + 13 + + + + + + cf.png + + + Call redirect indication. + + + + + statDndLabel + + + + 0 + 0 + 0 + 0 + + + + 13 + + + + + + cancel.png + + + Do not disturb indication. + + + + + statMWILabel + + + + 0 + 0 + 0 + 0 + + + + 13 + + + Plain + + + + + + mwi_none16.png + + + Message waiting indication. + + + + + statMissedLabel + + + + 0 + 0 + 0 + 0 + + + + 13 + + + + + + missed.png + + + Missed call indication. + + + + + statRegLabel + + + + 0 + 0 + 0 + 0 + + + + 13 + + + + + + twinkle16.png + + + Registration status. + + + + + + + + + + + displayGroupBox + + + + 5 + 5 + 0 + 0 + + + + Display + + + + unnamed + + + + displayTextEdit + + + + 7 + 3 + 0 + 0 + + + + AlwaysOn + + + PlainText + + + + + + NoWrap + + + true + + + AutoAll + + + + + + + lineButtonGroup + + + + 5 + 5 + 0 + 0 + + + + Line status + + + true + + + + unnamed + + + + layout20 + + + + unnamed + + + + layout27 + + + + unnamed + + + + line1RadioButton + + + + 0 + 0 + 0 + 0 + + + + Line &1: + + + Alt+1 + + + true + + + Click to switch to line 1. + + + + + fromhead1Label + + + + 0 + 5 + 0 + 0 + + + + From: + + + 21 + + + + + tohead1Label + + + + 0 + 5 + 0 + 0 + + + + To: + + + 21 + + + + + subjecthead1Label + + + + 0 + 5 + 0 + 0 + + + + Subject: + + + 21 + + + + + + + layout19 + + + + unnamed + + + + layout17 + + + + unnamed + + + + line1StatLabel + + + + 0 + 5 + 0 + 0 + + + + 0 + + + NoFrame + + + Plain + + + 1 + + + + + + stat_ringing.png + + + Visual indication of line state. + + + + + status1TextLabel + + + + 5 + 5 + 0 + 0 + + + + + 85 + 0 + 255 + + + + idle + No need to translate + + + RichText + + + + + line1HoldLabel + + + + 0 + 5 + 0 + 0 + + + + 0 + + + NoFrame + + + Plain + + + 1 + + + + + + hold.png + + + Call is on hold + + + + + line1MuteLabel + + + + 0 + 5 + 0 + 0 + + + + 0 + + + NoFrame + + + Plain + + + 1 + + + + + + stat_mute.png + + + Voice is muted + + + + + line1ConfLabel + + + + 0 + 5 + 0 + 0 + + + + 0 + + + NoFrame + + + Plain + + + 1 + + + + + + stat_conference.png + + + Conference call + + + + + line1ReferLabel + + + + 0 + 5 + 0 + 0 + + + + 0 + + + NoFrame + + + Plain + + + 1 + + + + + + cf.png + + + Transferring call + + + + + crypt1Label + + + + 0 + 5 + 0 + 0 + + + + 13 + + + Panel + + + Raised + + + 1 + + + + + + encrypted.png + + + <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> + + + + + line1SasLabel + + + + 0 + 5 + 0 + 0 + + + + sas + No need to translate + + + Short authentication string + + + + + codec1TextLabel + + + + 0 + 5 + 0 + 0 + + + + + 0 + 170 + 127 + + + + g711a/g711a + No need to translate + + + AlignVCenter|AlignRight + + + Audio codec + + + + + timer1TextLabel + + + + 0 + 5 + 0 + 0 + + + + + Courier New + + + + 0:00:00 + + + Call duration + + + + + + + from1Label + + + + 85 + 0 + 255 + + + + + 238 + 234 + 238 + + + + NoFocus + + + NoFrame + + + sip:from + No need to translate + + + AlignAuto + + + true + + + + + to1Label + + + + 85 + 0 + 255 + + + + + 238 + 234 + 238 + + + + NoFocus + + + NoFrame + + + sip:to + No need to translate + + + true + + + + + subject1Label + + + + 85 + 0 + 255 + + + + + 238 + 234 + 238 + + + + NoFocus + + + NoFrame + + + subject + No need to translate + + + true + + + + + + + photo1Label + + + + 0 + 0 + 0 + 0 + + + + + 70 + 98 + + + + + 70 + 98 + + + + Box + + + photo + No need to translate + + + + + + + layout22 + + + + unnamed + + + + layout31 + + + + unnamed + + + + line2RadioButton + + + + 0 + 0 + 0 + 0 + + + + Line &2: + + + Alt+2 + + + Click to switch to line 2. + + + + + fromhead2Label + + + + 0 + 5 + 0 + 0 + + + + From: + + + 21 + + + + + tohead2Label + + + + 0 + 5 + 0 + 0 + + + + To: + + + 21 + + + + + subjecthead2Label + + + + 0 + 5 + 0 + 0 + + + + Subject: + + + 21 + + + + + + + layout21 + + + + unnamed + + + + layout18 + + + + unnamed + + + + line2StatLabel + + + + 0 + 5 + 0 + 0 + + + + 0 + + + NoFrame + + + Plain + + + 1 + + + + + + stat_ringing.png + + + Visual indication of line state. + + + + + status2TextLabel + + + + 5 + 5 + 0 + 0 + + + + + 85 + 0 + 255 + + + + idle + No need to translate + + + RichText + + + + + line2HoldLabel + + + + 0 + 5 + 0 + 0 + + + + 0 + + + NoFrame + + + Plain + + + 1 + + + + + + hold.png + + + Call is on hold + + + + + line2MuteLabel + + + + 0 + 5 + 0 + 0 + + + + 0 + + + NoFrame + + + Plain + + + 1 + + + + + + stat_mute.png + + + Voice is muted + + + + + line2ConfLabel + + + + 0 + 5 + 0 + 0 + + + + 0 + + + NoFrame + + + Plain + + + 1 + + + + + + stat_conference.png + + + Conference call + + + + + line2ReferLabel + + + + 0 + 5 + 0 + 0 + + + + 0 + + + NoFrame + + + Plain + + + 1 + + + + + + cf.png + + + Transferring call + + + + + crypt2Label + + + + 0 + 5 + 0 + 0 + + + + 13 + + + Panel + + + Raised + + + 1 + + + + + + encrypted.png + + + <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> + + + + + line2SasLabel + + + + 0 + 5 + 0 + 0 + + + + sas + No need to translate + + + Short authentication string + + + + + codec2TextLabel + + + + 0 + 5 + 0 + 0 + + + + + 0 + 170 + 127 + + + + g711a/g711a + No need to translate + + + AlignVCenter|AlignRight + + + Audio codec + + + + + timer2TextLabel + + + + 0 + 5 + 0 + 0 + + + + + Courier New + + + + 0:00:00 + + + Call duration + + + + + + + from2Label + + + + 85 + 0 + 255 + + + + + 238 + 234 + 238 + + + + NoFocus + + + NoFrame + + + sip:from + No need to translate + + + true + + + + + to2Label + + + + 85 + 0 + 255 + + + + + 238 + 234 + 238 + + + + NoFocus + + + NoFrame + + + sip:to + No need to translate + + + true + + + + + subject2Label + + + + 85 + 0 + 255 + + + + + 238 + 234 + 238 + + + + NoFocus + + + NoFrame + + + subject + No need to translate + + + true + + + + + + + photo2Label + + + + 0 + 0 + 0 + 0 + + + + + 70 + 98 + + + + + 70 + 98 + + + + Box + + + photo + No need to translate + + + + + + + + + + + + + + menubar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + callToolbar + + + false + + + true + + + true + + + true + + + Call Toolbar + + + + + + + + + + + + + + + + + + + fileExitAction + + + exit.png + + + Quit + + + &Quit + + + Ctrl+Q + + + + + helpAboutAction + + + twinkle16.png + + + About Twinkle + + + &About Twinkle + + + + + + + + callInvite + + + invite.png + + + Call + toolbar text + + + &Call... + call menu text + + + Call someone + + + F5 + + + + + callAnswer + + + answer.png + + + Answer + toolbar text + + + &Answer + menu text + + + Answer incoming call + + + F6 + + + + + callBye + + + bye.png + + + Bye + toolbar text + + + &Bye + menu text + + + Release call + + + Esc + + + + + callReject + + + reject.png + + + Reject + toolbar text + + + &Reject + menu text + + + Reject incoming call + + + F8 + + + + + callHold + + + true + + + hold.png + + + Hold + toolbar text + + + &Hold + menu text + + + Put a call on hold, or retrieve a held call + + + + + callRedirect + + + redirect.png + + + Redirect + toolbar text + + + R&edirect... + menu text + + + Redirect incoming call without answering + + + + + callDTMF + + + dtmf.png + + + Dtmf + toolbar text + + + &Dtmf... + menu text + + + Open keypad to enter digits for voice menu's + + + + + regRegister + + + twinkle16.png + + + Register + + + &Register + + + + + regDeregister + + + twinkle16-disabled.png + + + Deregister + + + &Deregister + + + Deregister this device + + + + + regShow + + + reg-query.png + + + Show registrations + + + &Show registrations + + + + + callTermCap + + + Terminal capabilities + + + &Terminal capabilities... + menu text + + + Request terminal capabilities from someone + + + + + serviceDnd + + + true + + + cancel.png + + + Do not disturb + + + &Do not disturb + + + + + serviceRedirection + + + cf.png + + + Call redirection + + + Call &redirection... + + + + + callRedial + + + redial.png + + + Redial + toolbar text + + + &Redial + menu text + + + Repeat last call + + + F12 + + + + + helpAboutQtAction + + + qt-logo.png + + + About Qt + + + About &Qt + + + + + editUserProfileAction + + + penguin-small.png + + + User profile + + + &User profile... + + + + + callConference + + + conf.png + + + Conf + toolbar text + + + &Conference + menu text + + + Join two calls in a 3-way conference + + + + + callMute + + + true + + + mute.png + + + Mute + toolbar text + + + &Mute + menu text + + + Mute a call + + + + + callTransfer + + + true + + + transfer.png + + + Xfer + toolbar text + + + Trans&fer... + menu text + + + Transfer call + + + + + editSysSettingsAction + + + settings.png + + + System settings + + + &System settings... + + + + + registrationAction + + + + + + + + + + + regDeregisterAll + + + twinkle16-disabled.png + + + Deregister all + + + Deregister &all + + + Deregister all your registered devices + + + + + serviceAutoAnswer + + + true + + + auto_answer.png + + + Auto answer + + + &Auto answer + + + + + viewLogAction + + + log_small.png + + + Log + + + &Log... + + + + + viewCall_HistoryAction + + + missed.png + + + Call history + + + Call &history... + + + F9 + + + + + fileChangeUserAction + + + penguin-small.png + + + Change user ... + + + &Change user ... + + + Activate or de-activate users + + + + + helpWhats_ThisAction + + + contexthelp.png + + + What's This? + + + What's &This? + + + Shift+F1 + + + + + actgrActivateLine + + + false + + + Activate line + + + false + + + + actionLine1 + + + true + + + true + + + Line 1 + + + + + actionLine2 + + + true + + + Line 2 + + + + + + callActivate_LineAction + + + false + + + Activate line + + + Activate line + + + + + viewDisplayAction + + + true + + + true + + + Display + + + &Display + + + + + servicesVoice_mailAction + + + mwi_none16.png + + + Voice mail + + + &Voice mail + + + Access voice mail + + + F11 + + + + + actionSendMsg + + + message.png + + + Msg + + + Instant &message... + + + Instant message + + + + + viewBuddyListAction + + + true + + + true + + + Buddy list + + + &Buddy list + + + Buddy list + + + + + helpManualAction + + + Manual + + + &Manual + + + + + diamondcardSign_upAction + + + Sign up + + + &Sign up... + + + Sign up + + + + + + helpWhats_ThisAction + activated() + MphoneForm + whatsThis() + + + addressToolButton + clicked() + MphoneForm + showAddressBook() + + + callPushButton + clicked() + MphoneForm + quickCall() + + + fileChangeUserAction + activated() + MphoneForm + selectProfile() + + + viewCall_HistoryAction + activated() + MphoneForm + viewHistory() + + + viewLogAction + activated() + MphoneForm + viewLog() + + + regDeregisterAll + activated() + MphoneForm + phoneDeregisterAll() + + + editSysSettingsAction + activated() + MphoneForm + editSysSettings() + + + callMute + toggled(bool) + MphoneForm + phoneMute(bool) + + + callConference + activated() + MphoneForm + phoneConference() + + + editUserProfileAction + activated() + MphoneForm + editUserProfile() + + + helpAboutQtAction + activated() + MphoneForm + aboutQt() + + + helpAboutAction + activated() + MphoneForm + about() + + + callRedial + activated() + MphoneForm + phoneRedial() + + + serviceRedirection + activated() + MphoneForm + srvRedirect() + + + regShow + activated() + MphoneForm + phoneShowRegistrations() + + + regDeregister + activated() + MphoneForm + phoneDeregister() + + + regRegister + activated() + MphoneForm + phoneRegister() + + + callHold + toggled(bool) + MphoneForm + phoneHold(bool) + + + callReject + activated() + MphoneForm + phoneReject() + + + callRedirect + activated() + MphoneForm + phoneRedirect() + + + callInvite + activated() + MphoneForm + phoneInvite() + + + callDTMF + activated() + MphoneForm + phoneDTMF() + + + callBye + activated() + MphoneForm + phoneBye() + + + callAnswer + activated() + MphoneForm + phoneAnswer() + + + callTermCap + activated() + MphoneForm + phoneTermCap() + + + line2RadioButton + toggled(bool) + MphoneForm + line2rbChangedState(bool) + + + line1RadioButton + toggled(bool) + MphoneForm + line1rbChangedState(bool) + + + fileExitAction + activated() + MphoneForm + fileExit() + + + actionLine1 + toggled(bool) + MphoneForm + actionLine1Toggled(bool) + + + actionLine2 + toggled(bool) + MphoneForm + actionLine2Toggled(bool) + + + viewDisplayAction + toggled(bool) + MphoneForm + showDisplay(bool) + + + callTransfer + activated() + MphoneForm + phoneTransfer() + + + servicesVoice_mailAction + activated() + MphoneForm + popupMenuVoiceMail() + + + actionSendMsg + activated() + MphoneForm + startMessageSession() + + + buddyListView + rightButtonPressed(QListViewItem*,const QPoint&,int) + MphoneForm + showBuddyListPopupMenu(QListViewItem*,const QPoint&) + + + buddyListView + doubleClicked(QListViewItem*) + MphoneForm + doMessageBuddy(QListViewItem*) + + + viewBuddyListAction + toggled(bool) + MphoneForm + showBuddyList(bool) + + + helpManualAction + activated() + MphoneForm + manual() + + + diamondcardSign_upAction + activated() + MphoneForm + DiamondcardSignUp() + + + + callComboBox + addressToolButton + callPushButton + displayTextEdit + line1RadioButton + line2RadioButton + userComboBox + from1Label + subject1Label + to1Label + subject2Label + from2Label + to2Label + + + phone.h + dtmfform.h + inviteform.h + redirectform.h + termcapform.h + srvredirectform.h + userprofileform.h + transferform.h + syssettingsform.h + logviewform.h + historyform.h + selectuserform.h + selectprofileform.h + qevent.h + twinklesystray.h + im/msg_session.h + messageformview.h + buddylistview.h + diamondcard.h + user.h + qtextedit.h + qcheckbox.h + qapplication.h + gui.h + qpixmap.h + qiconset.h + qmessagebox.h + audits/memman.h + line.h + stun/stun_transaction.h + log.h + qprogressdialog.h + util.h + qtimer.h + sys/time.h + qframe.h + qcursor.h + qregexp.h + qvalidator.h + buddyform.h + diamondcardprofileform.h + mphoneform.ui.h + + + extern t_phone *phone; + + + 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; + t_twinkle_sys_tray *sysTray; + QTimer *lineTimer1; + QTimer *lineTimer2; + QTimer *hideLineTimer1; + QTimer *hideLineTimer2; + bool viewDisplay; + bool viewCompactLineStatus; + bool mwiFlashStatus; + QPopupMenu *buddyPopupMenu; + QPopupMenu *buddyListPopupMenu; + QPopupMenu *changeAvailabilityPopupMenu; + QToolTip *buddyToolTip; + bool viewBuddyList; + + + closeEvent( QCloseEvent * e ) + fileExit() + display( const QString & s ) + displayHeader() + showLineTimer( int line ) + showLineTimer1() + showLineTimer2() + updateLineTimer( int line ) + updateLineEncryptionState( int line ) + updateLineStatus( int line ) + updateState() + updateRegStatus() + flashMWI() + updateMwi() + updateServicesStatus() + updateMissedCallStatus( int num_missed_calls ) + updateSysTrayStatus() + updateMenuStatus() + updateDiamondcardMenu() + removeDiamondcardAction( int & id ) + removeDiamondcardMenu( QPopupMenu * & menu ) + phoneRegister() + do_phoneRegister( list<t_user *> user_list ) + phoneDeregister() + do_phoneDeregister( list<t_user *> user_list ) + phoneDeregisterAll() + do_phoneDeregisterAll( list<t_user *> user_list ) + phoneShowRegistrations() + phoneInvite( t_user * user_config, const QString & dest, const QString & subject, bool anonymous ) + phoneInvite( const QString & dest, const QString & subject, bool anonymous ) + phoneInvite() + do_phoneInvite( t_user * user_config, const QString & display, const t_url & destination, const QString & subject, bool anonymous ) + phoneRedial( void ) + phoneAnswer() + phoneAnswerFromSystrayPopup() + phoneBye() + phoneReject() + phoneRejectFromSystrayPopup() + phoneRedirect( const list<string> & contacts ) + phoneRedirect() + do_phoneRedirect( const list<t_display_url> & destinations ) + phoneTransfer( const string & dest, t_transfer_type transfer_type ) + phoneTransfer() + do_phoneTransfer( const t_display_url & destination, t_transfer_type transfer_type ) + do_phoneTransferLine() + phoneHold( bool on ) + phoneConference() + phoneMute( bool on ) + phoneTermCap( const QString & dest ) + phoneTermCap() + do_phoneTermCap( t_user * user_config, const t_url & destination ) + phoneDTMF() + sendDTMF( const QString & digits ) + startMessageSession( void ) + startMessageSession( t_buddy * buddy ) + phoneConfirmZrtpSas( int line ) + phoneConfirmZrtpSas() + phoneResetZrtpSasConfirmation( int line ) + phoneResetZrtpSasConfirmation() + phoneEnableZrtp( bool on ) + phoneZrtpGoClearOk( unsigned short line ) + line1rbChangedState( bool on ) + line2rbChangedState( bool on ) + actionLine1Toggled( bool on ) + actionLine2Toggled( bool on ) + srvDnd( bool on ) + srvDnd() + do_srvDnd_enable( list<t_user *> user_list ) + do_srvDnd_disable( list<t_user *> user_list ) + srvAutoAnswer( bool on ) + srvAutoAnswer() + do_srvAutoAnswer_enable( list<t_user *> user_list ) + do_srvAutoAnswer_disable( list<t_user *> user_list ) + srvRedirect() + do_srvRedirect( t_user * user_config, const list<t_display_url> & always, const list<t_display_url> & busy, const list<t_display_url> & noanswer ) + about() + aboutQt() + manual() + editUserProfile() + editSysSettings() + selectProfile() + newUsers( const list<string> & profiles ) + updateUserComboBox() + updateSipUdpPort() + updateRtpPorts() + updateStunSettings( t_user * user_config ) + updateAuthCache( t_user * user_config, const string & realm ) + unsubscribeMWI( t_user * user_config ) + subscribeMWI( t_user * user_config ) + viewLog() + updateLog( bool log_zapped ) + viewHistory() + updateCallHistory() + quickCall() + addToCallComboBox( const QString & destination ) + showAddressBook() + selectedAddress( const QString & address ) + enableCallOptions( bool enable ) + keyPressEvent( QKeyEvent * e ) + mouseReleaseEvent( QMouseEvent * e ) + processLeftMouseButtonRelease( QMouseEvent * e ) + processRightMouseButtonRelease( QMouseEvent * e ) + processCryptLabelClick( int line ) + popupMenuVoiceMail( const QPoint & pos ) + popupMenuVoiceMail( void ) + showDisplay( bool on ) + showBuddyList( bool on ) + showCompactLineStatus( bool on ) + populateBuddyList() + showBuddyListPopupMenu( QListViewItem * item, const QPoint & pos ) + doCallBuddy() + doMessageBuddy( QListViewItem * qitem ) + doMessageBuddy() + doEditBuddy() + doDeleteBuddy() + doAddBuddy() + doAvailabilityOffline() + doAvailabilityOnline() + DiamondcardSignUp() + newDiamondcardUser( const QString & filename ) + DiamondcardAction( t_dc_action action, int userIdx ) + DiamondcardRecharge( int userIdx ) + DiamondcardBalanceHistory( int userIdx ) + DiamondcardCallHistory( int userIdx ) + DiamondcardAdminCenter( int userIdx ) + + + init() + destroy() + lineSubstate2str( int line ) + getMWIStatus( const t_mwi & mwi, bool & msg_waiting ) const + getSysTray() + getViewDisplay() + getViewBuddyList() + getViewCompactLineStatus() + + + + diff --git a/src/gui/mphoneform.ui.h b/src/gui/mphoneform.ui.h new file mode 100644 index 0000000..6d008e6 --- /dev/null +++ b/src/gui/mphoneform.ui.h @@ -0,0 +1,3126 @@ +/**************************************************************************** +** 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "twinkle_config.h" +#include "twinklesystray.h" + +// Time (s) that the conversation timer of a line should stay visible after +// a call has ended +#define HIDE_LINE_TIMER_AFTER 5 + +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 + QIconSet inviteIcon(QPixmap::fromMimeSource("invite.png")); + QIconSet messageIcon(QPixmap::fromMimeSource("message.png")); + QIconSet editIcon(QPixmap::fromMimeSource("edit16.png")); + QIconSet deleteIcon(QPixmap::fromMimeSource("editdelete.png")); + buddyPopupMenu = new QPopupMenu(this); + MEMMAN_NEW(buddyPopupMenu); + buddyPopupMenu->insertItem(inviteIcon, tr("&Call..."), this, SLOT(doCallBuddy())); + buddyPopupMenu->insertItem(messageIcon, tr("Instant &message..."), this, SLOT(doMessageBuddy())); + buddyPopupMenu->insertItem(editIcon, tr("&Edit..."), this, SLOT(doEditBuddy())); + buddyPopupMenu->insertItem(deleteIcon, tr("&Delete"), this, SLOT(doDeleteBuddy())); + + // Change availibility sub popup menu + changeAvailabilityPopupMenu = new QPopupMenu(this); + MEMMAN_NEW(changeAvailabilityPopupMenu); + QIconSet availOnlineIcon(QPixmap::fromMimeSource("presence_online.png")); + QIconSet availOfflineIcon(QPixmap::fromMimeSource("presence_offline.png")); + changeAvailabilityPopupMenu->insertItem(availOfflineIcon, tr("O&ffline"), this, + SLOT(doAvailabilityOffline())); + changeAvailabilityPopupMenu->insertItem(availOnlineIcon, tr("&Online"), this, + SLOT(doAvailabilityOnline())); + + // Popup menu for a buddy list (click on profile name) + QIconSet changeAvailabilityIcon(QPixmap::fromMimeSource("presence_online.png")); + QIconSet addIcon(QPixmap::fromMimeSource("buddy.png")); + buddyListPopupMenu = new QPopupMenu(this); + MEMMAN_NEW(buddyListPopupMenu); + buddyListPopupMenu->insertItem(changeAvailabilityIcon, tr("&Change availability"), + changeAvailabilityPopupMenu); + buddyListPopupMenu->insertItem(addIcon, tr("&Add buddy..."), this, SLOT(doAddBuddy())); + + // Tool tip for buddy list + buddyToolTip = new BuddyListViewTip(buddyListView); + MEMMAN_NEW(buddyToolTip); + + // 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()); + + // 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 t_twinkle_sys_tray(this, "twinkle_sys_tray"); + MEMMAN_NEW(sysTray); + sysTray->setPixmap( + QPixmap::fromMimeSource("sys_idle_dis.png")); + sysTray->setCaption(PRODUCT_NAME); + QToolTip::add(sysTray, PRODUCT_NAME); + + // Add items to the system tray menu +#ifdef HAVE_KDE + KPopupMenu *menu; +#else + QPopupMenu *menu; +#endif + menu = sysTray->contextMenu(); + + // Call menu + callInvite->addTo(menu); + callAnswer->addTo(menu); + callBye->addTo(menu); + callReject->addTo(menu); + callRedirect->addTo(menu); + callTransfer->addTo(menu); + callHold->addTo(menu); + callConference->addTo(menu); + callMute->addTo(menu); + callDTMF->addTo(menu); + callRedial->addTo(menu); + + menu->insertSeparator(); + + // Messaging + actionSendMsg->addTo(menu); + + menu->insertSeparator(); + + // Line activation + actgrActivateLine->addTo(menu); + + menu->insertSeparator(); + + // Service menu + serviceDnd->addTo(menu); + serviceRedirection->addTo(menu); + serviceAutoAnswer->addTo(menu); + servicesVoice_mailAction->addTo(menu); + + menu->insertSeparator(); + + // View menu + viewCall_HistoryAction->addTo(menu); + + menu->insertSeparator(); + + // Diamondcard menu + menu->insertItem("Diamondcard", Diamondcard); + + // Exit application when user selects Quit from the tray menu + connect(sysTray, SIGNAL(quitSelected()), + this, SLOT(fileExit())); + + sysTray->dock(); + sysTray->show(); + } +} + +void MphoneForm::destroy() +{ + 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->isShown()) logViewForm->close(); + MEMMAN_DELETE(logViewForm); + delete logViewForm; + } + if (historyForm) { + if (historyForm->isShown()) 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(changeAvailabilityPopupMenu); + delete changeAvailabilityPopupMenu; + MEMMAN_DELETE(buddyListPopupMenu); + delete buddyListPopupMenu; + MEMMAN_DELETE(buddyToolTip); + delete buddyToolTip; +} + +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(); + QApplication::exit(0); +} + +// 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 + displayTextEdit->setCursorPosition(displayTextEdit->paragraphs() - 1, 0); +} + +// 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()); +} + +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)->start(1000, false); + } + break; + default: + // Hide call duration timer + if (*timer != NULL) { + // Hide the timer after a few seconds + hideLineTimer->start(HIDE_LINE_TIMER_AFTER * 1000, true); + (*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(); + + QToolTip::remove(cryptLabel); + 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::fromMimeSource("encrypted.png")); + } else { + toolTip.append("\n").append(tr("Click to clear SAS verification.")); + cryptLabel->setFrameStyle(QFrame::NoFrame); + cryptLabel->setPixmap( + QPixmap::fromMimeSource("encrypted_verified.png")); + } + + QToolTip::add(cryptLabel, 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; + QToolTip::remove(referLabel); + referLabel->show(); + if (is_transfer_consult) { + referLabel->setPixmap( + QPixmap::fromMimeSource("consult-xfer.png")); + toolTip = tr("Transfer consultation"); + } else { + referLabel->setPixmap( + QPixmap::fromMimeSource("cf.png")); + toolTip = tr("Transferring call"); + } + QToolTip::add(referLabel, 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::fromMimeSource("stat_outgoing.png")); + statLabel->show(); + break; + case LSSUB_INCOMING_PROGRESS: + statLabel->setPixmap(QPixmap::fromMimeSource("stat_ringing.png")); + statLabel->show(); + break; + case LSSUB_ANSWERING: + statLabel->setPixmap(QPixmap::fromMimeSource("gear.png")); + statLabel->show(); + break; + case LSSUB_ESTABLISHED: + if (phone->has_line_media(line)) { + statLabel->setPixmap(QPixmap::fromMimeSource( + "stat_established.png")); + } else { + statLabel->setPixmap(QPixmap::fromMimeSource( + "stat_established_nomedia.png")); + } + statLabel->show(); + break; + case LSSUB_RELEASING: + statLabel->setPixmap(QPixmap::fromMimeSource("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->isOn()) + { + line1RadioButton->setChecked(true); + } else if (line == 1 && line1RadioButton->isOn()) + { + line2RadioButton->setChecked(true); + } + + // Same logic for the activate line menu items + if (line == 0 && actionLine2->isOn()) + { + actionLine1->setOn(true); + } else if (line == 1 && actionLine1->isOn()) + { + actionLine2->setOn(true); + } + + 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); + 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()); + } + + // Set hold action in correct state + callHold->setOn(on_hold); + + // Set mute action in correct state + callMute->setOn(is_muted); + + // Set transfer action in correct state + callTransfer->setOn(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 succesful 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::fromMimeSource("twinkle16.png")); + } else if (num_failed == user_list.size()) { + // All users failed to register + statRegLabel->setPixmap(QPixmap::fromMimeSource("reg_failed.png")); + } else if (num_registered > 0) { + // Some users are registered + statRegLabel->setPixmap(QPixmap::fromMimeSource( + "twinkle16.png")); + } else if (num_failed > 0) { + // Some users failed, none are registered + statRegLabel->setPixmap(QPixmap::fromMimeSource("reg_failed.png")); + } else { + // No users are registered, no users failed + statRegLabel->setPixmap(QPixmap::fromMimeSource("twinkle16-disabled.png")); + } + + // Set tool tip with detailed info. + QToolTip::remove(statRegLabel); + + if (num_registered > 0 || num_failed > 0) { + QToolTip::add(statRegLabel, toolTip); + } else { + QToolTip::add(statRegLabel, 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::fromMimeSource( + "mwi_none16.png")); + } else { + mwiFlashStatus = true; + statMWILabel->setPixmap(QPixmap::fromMimeSource( + "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::fromMimeSource( + "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::fromMimeSource( + "mwi_failure16.png")); + } else if (mwi_known) { + tmrFlashMWI.stop(); + statMWILabel->setPixmap(QPixmap::fromMimeSource( + "mwi_none16.png")); + } else { + tmrFlashMWI.stop(); + statMWILabel->setPixmap(QPixmap::fromMimeSource( + "mwi_none16_dis.png")); + } + + // Set tool tip + QToolTip::remove(statMWILabel); + QToolTip::add(statMWILabel, 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::fromMimeSource("cancel.png")); + } else if (num_dnd > 0) { + // Some users enabled dnd + statDndLabel->setPixmap(QPixmap::fromMimeSource("cancel.png")); + } else { + // No users enabeld dnd + statDndLabel->setPixmap(QPixmap::fromMimeSource("cancel-disabled.png")); + } + + if (num_cf == user_list.size()) { + // All users enabled redirecton + statCfLabel->setPixmap(QPixmap::fromMimeSource("cf.png")); + } else if (num_cf > 0) { + // Some users enabled redirection + statCfLabel->setPixmap(QPixmap::fromMimeSource("cf.png")); + } else { + // No users enabled redirection + statCfLabel->setPixmap(QPixmap::fromMimeSource("cf-disabled.png")); + } + + if (num_auto_answer == user_list.size()) { + // All users enabled auto answer + statAaLabel->setPixmap(QPixmap::fromMimeSource("auto_answer.png")); + } else if (num_auto_answer > 0) { + // Some users enabled auto answer + statAaLabel->setPixmap(QPixmap::fromMimeSource( + "auto_answer.png")); + } else { + // No users enabeld auto answer + statAaLabel->setPixmap(QPixmap::fromMimeSource( + "auto_answer-disabled.png")); + } + + // Set tool tip with detailed info for multiple users. + QToolTip::remove(statDndLabel); + QToolTip::remove(statCfLabel); + QToolTip::remove(statAaLabel); + + QString clickToActivate(""); + clickToActivate += tr("Click to activate").replace(' ', " "); + clickToActivate += ""; + if (num_dnd > 0) { + QToolTip::add(statDndLabel, tipDnd); + } else { + QString status("

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

"; + status += clickToActivate; + QToolTip::add(statDndLabel, status); + } + + if (num_cf > 0) { + QToolTip::add(statCfLabel, tipCf); + } else { + QString status("

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

"; + status += clickToActivate; + QToolTip::add(statCfLabel, status); + } + + if (num_auto_answer > 0) { + QToolTip::add(statAaLabel, tipAa); + } else { + QString status("

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

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

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

"; + status += clickDetails; + QToolTip::add(statMissedLabel, status); + } else { + statMissedLabel->setPixmap( + QPixmap::fromMimeSource("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; + QToolTip::add(statMissedLabel, 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->setPixmap(QPixmap::fromMimeSource(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->setToggleAction(true); + serviceDnd->setOn(srv->is_dnd_active()); + connect(serviceDnd, SIGNAL(toggled(bool)), + this, SLOT(srvDnd(bool))); + + serviceAutoAnswer->setToggleAction(true); + serviceAutoAnswer->setOn(srv->is_auto_answer_active()); + connect(serviceAutoAnswer, SIGNAL(toggled(bool)), + this, SLOT(srvAutoAnswer(bool))); + } else { + serviceDnd->setOn(false); + serviceDnd->setToggleAction(false); + connect(serviceDnd, SIGNAL(activated()), + this, SLOT(srvDnd())); + + serviceAutoAnswer->setOn(false); + serviceAutoAnswer->setToggleAction(false); + connect(serviceAutoAnswer, SIGNAL(activated()), + this, SLOT(srvAutoAnswer())); + } + + updateDiamondcardMenu(); +} + +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 int rechargeId = -1; + static int balanceHistoryId = -1; + static int callHistoryId = -1; + static int adminCenterId = -1; + + // Sub menu's + static QPopupMenu *rechargeMenu = NULL; + static QPopupMenu *balanceHistoryMenu = NULL; + static QPopupMenu *callHistoryMenu = NULL; + static QPopupMenu *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->insertItem(tr("Recharge..."), this, SLOT(DiamondcardRecharge(int))); + Diamondcard->setItemParameter(rechargeId, 0); + balanceHistoryId = Diamondcard->insertItem(tr("Balance history..."), this, SLOT(DiamondcardBalanceHistory(int))); + Diamondcard->setItemParameter(balanceHistoryId, 0); + callHistoryId = Diamondcard->insertItem(tr("Call history..."), this, SLOT(DiamondcardCallHistory(int))); + Diamondcard->setItemParameter(callHistoryId, 0); + adminCenterId = Diamondcard->insertItem(tr("Admin center..."), this, SLOT(DiamondcardAdminCenter(int))); + Diamondcard->setItemParameter(adminCenterId, 0); + + // Disable actions as there is no active Diamondcard users. + if (diamondcard_users.empty()) { + Diamondcard->setItemEnabled(rechargeId, false); + Diamondcard->setItemEnabled(balanceHistoryId, false); + Diamondcard->setItemEnabled(callHistoryId, false); + Diamondcard->setItemEnabled(adminCenterId, false); + } + } + else + { + rechargeMenu = new QPopupMenu(this); + balanceHistoryMenu = new QPopupMenu(this); + callHistoryMenu = new QPopupMenu(this); + adminCenterMenu = new QPopupMenu(this); + // 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) + { + int 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->insertItem(user->get_profile_name().c_str(), this, + SLOT(DiamondcardRecharge(int))); + rechargeMenu->setItemParameter(menuId, idx); + menuId = balanceHistoryMenu->insertItem(user->get_profile_name().c_str(), this, + SLOT(DiamondcardBalanceHistory(int))); + balanceHistoryMenu->setItemParameter(menuId, idx); + menuId = callHistoryMenu->insertItem(user->get_profile_name().c_str(), this, + SLOT(DiamondcardCallHistory(int))); + callHistoryMenu->setItemParameter(menuId, idx); + menuId = adminCenterMenu->insertItem(user->get_profile_name().c_str(), this, + SLOT(DiamondcardAdminCenter(int))); + adminCenterMenu->setItemParameter(menuId, idx); + + ++idx; + } + + // Add the Diamondcard popup menus to the main Diamondcard menu. + Diamondcard->insertItem(tr("Recharge"), rechargeMenu); + Diamondcard->insertItem(tr("Balance history"), balanceHistoryMenu); + Diamondcard->insertItem(tr("Call history"), callHistoryMenu); + Diamondcard->insertItem(tr("Admin center"), adminCenterMenu); + } +} + +void MphoneForm::removeDiamondcardAction(int &id) +{ + if (id != -1) { + Diamondcard->removeItem(id); + id = -1; + } +} + +void MphoneForm::removeDiamondcardMenu(QPopupMenu* &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, "register", 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, "deregister", 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, "deregister all", 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, "invite", true); + MEMMAN_NEW(inviteForm); + + // Initialize the destination history list + for (int i = callComboBox->count() - 1; i >= 0; i--) { + inviteForm->addToInviteComboBox(callComboBox->text(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().ascii()); + 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().ascii()); + 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.ascii(), subject.ascii(), + 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, "redirect", 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, "transfer", 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, "termcap", 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().ascii()); + 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.ascii()); +} + +void MphoneForm::startMessageSession(void) +{ + t_user *user = phone->ref_user_profile(userComboBox->currentText().ascii()); + 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, "dnd", 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, "auto answer", 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, "call redirection", 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::fromMimeSource("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, "user profile", 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, "system settings", 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, "select profile", 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.ascii())) { + add_profile_list.push_back(*i); + } + } + + // Add new phone users + QProgressDialog progress(tr("Starting user profiles..."), "Abort", add_profile_list.size(), this, + "starting user profiles", true); + progress.setCaption(PRODUCT_NAME); + progress.setMinimumDuration(200); + int progressStep = 0; + for (list::iterator i = add_profile_list.begin(); i != add_profile_list.end(); i++) { + progress.setProgress(progressStep); + qApp->processEvents(); + + if (progress.wasCancelled()) { + 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(user_config.get_name().c_str()).ascii(); + 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."); + + 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.setProgress(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->text(i) == current_user) { + userComboBox->setCurrentItem(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.").ascii(), + 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(); +} + +t_twinkle_sys_tray *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().ascii()); + 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().stripWhiteSpace().ascii(), + 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->text(i) == destination) { + callComboBox->removeItem(i); + } + } + + // Add entry + callComboBox->insertItem(destination, 0); + callComboBox->setCurrentItem(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->clearEdit(); +} + +void MphoneForm::showAddressBook() +{ + if (!getAddressForm) { + getAddressForm = new GetAddressForm( + this, "select address", 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->hasMouse()) { + if (phone->ref_users().size() == 1) { + bool enable = !serviceAutoAnswer->isOn(); + srvAutoAnswer(enable); + serviceAutoAnswer->setOn(enable); + } else { + srvAutoAnswer(); + } + } else if (statDndLabel->hasMouse()) { + if (phone->ref_users().size() == 1) { + bool enable = !serviceDnd->isOn(); + srvDnd(enable); + serviceDnd->setOn(enable); + } else { + srvDnd(); + } + } else if (statCfLabel->hasMouse()) { + srvRedirect(); + } else if (statMWILabel->hasMouse()) { + popupMenuVoiceMail(e->globalPos()); + } else if (statMissedLabel->hasMouse()) { + // Open the history form, when the user clicks on the + // missed calls indication. + viewHistory(); + } else if (statRegLabel->hasMouse()) { + // Fetch registration status + phoneShowRegistrations(); + } else if (crypt1Label->hasMouse()) { + processCryptLabelClick(0); + } else if (crypt2Label->hasMouse()) { + 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) +{ + QPopupMenu menu(this); + QIconSet vmIcon(QPixmap::fromMimeSource("mwi_none16.png")); + vmIcon.setPixmap(QPixmap::fromMimeSource("mwi_none16_dis.png"), + QIconSet::Automatic, QIconSet::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; + } + + int id = menu.insertItem(vmIcon, entry); + if (address.isEmpty()) { + menu.setItemEnabled(id, false); + } + + vm.insert(make_pair(id, *i)); + } + + int 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 == -1) 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.").ascii(), + 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.").ascii(), + 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()).ascii(), + 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->setOn(on); +} + +void MphoneForm::showBuddyList(bool on) +{ + if (on) { + buddyListView->show(); + } else { + buddyListView->hide(); + } + + viewBuddyList = on; + viewBuddyListAction->setOn(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); + } +} + +void MphoneForm::showBuddyListPopupMenu(QListViewItem *item, const QPoint &pos) +{ + if (!item) return; + + BuddyListViewItem *buddyItem = dynamic_cast(item); + if (buddyItem) { + buddyPopupMenu->popup(pos); + } else { + buddyListPopupMenu->popup(pos); + } +} + +void MphoneForm::doCallBuddy() +{ + QListViewItem *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(QListViewItem *qitem) +{ + BuddyListViewItem *item = dynamic_cast(qitem); + if (!item) return; + + t_buddy *buddy = item->get_buddy(); + + startMessageSession(buddy); +} + +void MphoneForm::doMessageBuddy() +{ + QListViewItem *item = buddyListView->currentItem(); + doMessageBuddy(item); +} + +void MphoneForm::doEditBuddy() +{ + QListViewItem *qitem = buddyListView->currentItem(); + BuddyListViewItem *item = dynamic_cast(qitem); + if (!item) return; + + t_buddy *buddy = item->get_buddy(); + + BuddyForm *form = new BuddyForm(this, "new_buddy", true, Qt::WDestructiveClose); + // Do not call MEMMAN as this form will be deleted automatically. + form->showEdit(*buddy); +} + +void MphoneForm::doDeleteBuddy() +{ + QListViewItem *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.ascii(), MSG_CRITICAL); + } +} + +void MphoneForm::doAddBuddy() +{ + QListViewItem *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, "new_buddy", true, Qt::WDestructiveClose); + // Do not call MEMMAN as this form will be deleted automatically. + form->showNew(*buddy_list, item); +} + +void MphoneForm::doAvailabilityOffline() +{ + QListViewItem *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() +{ + QListViewItem *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, "select profile", true, + Qt::WDestructiveClose); + + 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.ascii()); + 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(int userIdx) +{ + DiamondcardAction(DC_ACT_RECHARGE, userIdx); +} + +void MphoneForm::DiamondcardBalanceHistory(int userIdx) +{ + DiamondcardAction(DC_ACT_BALANCE_HISTORY, userIdx); +} + +void MphoneForm::DiamondcardCallHistory(int userIdx) +{ + DiamondcardAction(DC_ACT_CALL_HISTORY, userIdx); +} + +void MphoneForm::DiamondcardAdminCenter(int userIdx) +{ + DiamondcardAction(DC_ACT_ADMIN_CENTER, userIdx); +} diff --git a/src/gui/numberconversionform.ui b/src/gui/numberconversionform.ui new file mode 100644 index 0000000..5ad490c --- /dev/null +++ b/src/gui/numberconversionform.ui @@ -0,0 +1,175 @@ + +NumberConversionForm + + + NumberConversionForm + + + + 0 + 0 + 436 + 122 + + + + Twinkle - Number conversion + + + + unnamed + + + + layout43 + + + + unnamed + + + + exprTextLabel + + + &Match expression: + + + exprLineEdit + + + + + replaceTextLabel + + + &Replace: + + + replaceLineEdit + + + + + replaceLineEdit + + + Perl style format string for the replacement number. + + + + + exprLineEdit + + + Perl style regular expression matching the number format you want to modify. + + + + + + + spacer62 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + layout44 + + + + unnamed + + + + spacer61 + + + Horizontal + + + Expanding + + + + 71 + 20 + + + + + + okPushButton + + + &OK + + + Alt+O + + + + + cancelPushButton + + + &Cancel + + + Alt+C + + + + + + + + + cancelPushButton + clicked() + NumberConversionForm + reject() + + + okPushButton + clicked() + NumberConversionForm + validate() + + + + exprLineEdit + replaceLineEdit + okPushButton + cancelPushButton + + + qstring.h + gui.h + boost/regex.hpp + qregexp.h + qvalidator.h + numberconversionform.ui.h + + + validate() + + + init() + exec( QString & expr, QString & replace ) + + + + diff --git a/src/gui/numberconversionform.ui.h b/src/gui/numberconversionform.ui.h new file mode 100644 index 0000000..6bbdaec --- /dev/null +++ b/src/gui/numberconversionform.ui.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +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.").ascii(), MSG_CRITICAL); + exprLineEdit->setFocus(); + exprLineEdit->selectAll(); + return; + } + + if (replace.isEmpty()) { + ((t_gui *)ui)->cb_show_msg(this, + tr("Replace value may not be empty.").ascii(), MSG_CRITICAL); + replaceLineEdit->setFocus(); + replaceLineEdit->selectAll(); + return; + } + + try { + boost::regex re(expr.ascii()); + } catch (boost::bad_expression) { + ((t_gui *)ui)->cb_show_msg(this, + tr("Invalid regular expression.").ascii(), MSG_CRITICAL); + exprLineEdit->setFocus(); + return; + } + + accept(); +} diff --git a/src/gui/qt_translator.h b/src/gui/qt_translator.h new file mode 100644 index 0000000..c896d4c --- /dev/null +++ b/src/gui/qt_translator.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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()).ascii(); + }; + + virtual string translate2(const string &context, const string &s) { + return _qa->translate(context.c_str(), s.c_str()).ascii(); + }; + +private: + QApplication *_qa; +}; + +#endif diff --git a/src/gui/redirectform.ui b/src/gui/redirectform.ui new file mode 100644 index 0000000..e3f1e53 --- /dev/null +++ b/src/gui/redirectform.ui @@ -0,0 +1,308 @@ + +RedirectForm + + + RedirectForm + + + + 0 + 0 + 588 + 190 + + + + + 5 + 5 + 0 + 0 + + + + Twinkle - Redirect + + + + unnamed + + + + redirectGroupBox + + + Redirect incoming call to + + + + unnamed + + + + contact3LineEdit + + + 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. + + + + + contact3TextLabel + + + &3rd choice destination: + + + contact3LineEdit + + + + + contact2TextLabel + + + &2nd choice destination: + + + contact2LineEdit + + + + + contact2LineEdit + + + 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. + + + + + contact1LineEdit + + + 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. + + + + + contact1TextLabel + + + &1st choice destination: + + + contact1LineEdit + + + + + address1ToolButton + + + TabFocus + + + + + + F10 + + + kontact_contacts.png + + + Address book + + + Select an address from the address book. + + + + + address3ToolButton + + + TabFocus + + + + + + F12 + + + kontact_contacts.png + + + Address book + + + Select an address from the address book. + + + + + address2ToolButton + + + TabFocus + + + + + + F11 + + + kontact_contacts.png + + + Address book + + + Select an address from the address book. + + + + + + + spacer14 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + layout15 + + + + unnamed + + + + spacer11 + + + Horizontal + + + Expanding + + + + 361 + 20 + + + + + + okPushButton + + + &OK + + + true + + + + + cancelPushButton + + + &Cancel + + + + + + + + + cancelPushButton + clicked() + RedirectForm + reject() + + + okPushButton + clicked() + RedirectForm + validate() + + + address1ToolButton + clicked() + RedirectForm + showAddressBook1() + + + address2ToolButton + clicked() + RedirectForm + showAddressBook2() + + + address3ToolButton + clicked() + RedirectForm + showAddressBook3() + + + + contact1LineEdit + contact2LineEdit + contact3LineEdit + address1ToolButton + address2ToolButton + address3ToolButton + okPushButton + cancelPushButton + + + gui.h + audits/memman.h + list + sockets/url.h + getaddressform.h + user.h + redirectform.ui.h + + + GetAddressForm *getAddressForm; + int nrAddressBook; + t_user *user_config; + + + destinations(const list<t_display_url> &) + + + show( t_user * user, const list<string> & contacts ) + validate() + showAddressBook() + showAddressBook1() + showAddressBook2() + showAddressBook3() + selectedAddress( const QString & address ) + + + init() + destroy() + + + + diff --git a/src/gui/redirectform.ui.h b/src/gui/redirectform.ui.h new file mode 100644 index 0000000..d29a874 --- /dev/null +++ b/src/gui/redirectform.ui.h @@ -0,0 +1,157 @@ +/**************************************************************************** +** 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, preserving your code. Create 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +void RedirectForm::init() +{ + // Keeps track of which address book tool button is clicked. + nrAddressBook = 0; + + getAddressForm = 0; + + // Set toolbutton icons for disabled options. + QIconSet i; + i = address1ToolButton->iconSet(); + i.setPixmap(QPixmap::fromMimeSource("kontact_contacts-disabled.png"), + QIconSet::Automatic, QIconSet::Disabled); + address1ToolButton->setIconSet(i); + address2ToolButton->setIconSet(i); + address3ToolButton->setIconSet(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().stripWhiteSpace().ascii(), + 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().stripWhiteSpace().ascii(), 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().stripWhiteSpace().ascii(), 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, "select address", 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; + } +} diff --git a/src/gui/selectnicform.ui b/src/gui/selectnicform.ui new file mode 100644 index 0000000..7f10220 --- /dev/null +++ b/src/gui/selectnicform.ui @@ -0,0 +1,212 @@ + +SelectNicForm + + + SelectNicForm + + + + 0 + 0 + 482 + 144 + + + + Twinkle - Select NIC + + + + unnamed + + + + nicIconTextLabel + + + + + + kcmpci.png + + + + + spacer53 + + + Vertical + + + Expanding + + + + 20 + 41 + + + + + + layout42 + + + + unnamed + + + + selectTextLabel + + + Select the network interface/IP address that you want to use: + + + WordBreak|AlignTop + + + + + nicListBox + + + 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. + + + + + + + spacer9 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + layout11 + + + + unnamed + + + + spacer7 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + defaultIpPushButton + + + 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. + + + + + defaultNicPushButton + + + 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. + + + + + okPushButton + + + &OK + + + Alt+O + + + true + + + + + + + + + okPushButton + clicked() + SelectNicForm + accept() + + + nicListBox + selected(const QString&) + SelectNicForm + accept() + + + defaultIpPushButton + clicked() + SelectNicForm + setAsDefaultIp() + + + defaultNicPushButton + clicked() + SelectNicForm + setAsDefaultNic() + + + + gui.h + qmessagebox.h + sys_settings.h + selectnicform.ui.h + + + int idxDefault; + + + setAsDefault( bool setIp ) + setAsDefaultIp() + setAsDefaultNic() + + + init() + + + + diff --git a/src/gui/selectnicform.ui.h b/src/gui/selectnicform.ui.h new file mode 100644 index 0000000..9dc0a35 --- /dev/null +++ b/src/gui/selectnicform.ui.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** 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, preserving your code. Create 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +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::fromMimeSource("kcmpci16.png"), + nicListBox->text(idxDefault), + idxDefault); + } + + // Set pixmap of the default + idxDefault = idxNewDefault; + nicListBox->changeItem( + QPixmap::fromMimeSource("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); +} diff --git a/src/gui/selectprofileform.ui b/src/gui/selectprofileform.ui new file mode 100644 index 0000000..9efb522 --- /dev/null +++ b/src/gui/selectprofileform.ui @@ -0,0 +1,414 @@ + +SelectProfileForm + + + SelectProfileForm + + + + 0 + 0 + 501 + 513 + + + + Twinkle - Select user profile + + + + unnamed + + + + selectTextLabel + + + + 5 + 0 + 0 + 0 + + + + Select user profile(s) to run: + + + WordBreak|AlignVCenter + + + + + + User profile + + + true + + + true + + + + profileListView + + + LastColumn + + + Tick the check boxes of the user profiles that you want to run and press run. + + + + + layout78 + + + + unnamed + + + + spacer17 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + newProfileGroupBox + + + Create profile + + + + unnamed + + + + newPushButton + + + Ed&itor + + + Alt+I + + + Create a new profile with the profile editor. + + + + + wizardPushButton + + + &Wizard + + + Alt+W + + + Create a new profile with the wizard. + + + + + diamondcardPushButton + + + Dia&mondcard + + + Alt+M + + + 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. + + + + + + + modifyProfileGroupBox + + + Modify profile + + + + unnamed + + + + editPushButton + + + &Edit + + + Alt+E + + + Edit the highlighted profile. + + + + + deletePushButton + + + &Delete + + + Alt+D + + + Delete the highlighted profile. + + + + + renamePushButton + + + Ren&ame + + + Alt+A + + + Rename the highlighted profile. + + + + + + + startupProfileGroupBox + + + Startup profile + + + + unnamed + + + + defaultPushButton + + + &Set as default + + + Alt+S + + + Make the selected profiles the default profiles. The next time you start Twinkle, these profiles will be automatically run. + + + + + runPushButton + + + &Run + + + Alt+R + + + true + + + Run Twinkle with the selected profiles. + + + + + + + sysPushButton + + + S&ystem settings + + + Alt+Y + + + Edit the system settings. + + + + + cancelPushButton + + + &Cancel + + + Alt+C + + + + + + + + + cancelPushButton + clicked() + SelectProfileForm + reject() + + + runPushButton + clicked() + SelectProfileForm + runProfile() + + + editPushButton + clicked() + SelectProfileForm + editProfile() + + + newPushButton + clicked() + SelectProfileForm + newProfile() + + + deletePushButton + clicked() + SelectProfileForm + deleteProfile() + + + renamePushButton + clicked() + SelectProfileForm + renameProfile() + + + wizardPushButton + clicked() + SelectProfileForm + wizardProfile() + + + defaultPushButton + clicked() + SelectProfileForm + setAsDefault() + + + sysPushButton + clicked() + SelectProfileForm + sysSettings() + + + profileListView + doubleClicked(QListViewItem*) + SelectProfileForm + toggleItem(QListViewItem*) + + + diamondcardPushButton + clicked() + SelectProfileForm + diamondcardProfile() + + + + profileListView + newPushButton + wizardPushButton + diamondcardPushButton + editPushButton + deletePushButton + renamePushButton + defaultPushButton + runPushButton + sysPushButton + cancelPushButton + + + list + string + phone.h + qmainwindow.h + qdir.h + user.h + qstringlist.h + qmessagebox.h + protocol.h + gui.h + userprofileform.h + getprofilenameform.h + audits/memman.h + wizardform.h + syssettingsform.h + qlistview.h + cstring + service.h + presence/buddy.h + diamondcardprofileform.h + selectprofileform.ui.h + + + extern t_phone *phone; + + + std::list<std::string> selectedProfiles; + bool defaultSet; + t_user *user_config; + QMainWindow *mainWindow; + + + selection(const list<string> &) + profileRenamed() + + + showForm( QMainWindow * _mainWindow ) + runProfile() + editProfile() + newProfile() + newProfile( bool exec_mode ) + newProfileCreated() + deleteProfile() + renameProfile() + setAsDefault() + wizardProfile() + wizardProfile( bool exec_mode ) + diamondcardProfile() + diamondcardProfile( bool exec_mode ) + sysSettings() + fillProfileListView( const QStringList & profiles ) + toggleItem( QListViewItem * item ) + + + init() + destroy() + execForm() + getUserProfiles( QStringList & profiles, QString & error ) + + + + diff --git a/src/gui/selectprofileform.ui.h b/src/gui/selectprofileform.ui.h new file mode 100644 index 0000000..1586227 --- /dev/null +++ b/src/gui/selectprofileform.ui.h @@ -0,0 +1,631 @@ +/**************************************************************************** +** 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, preserving your code. Create 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +void SelectProfileForm::init() +{ + user_config = 0; +} + +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.

"\ + "You can create a Diamondcard account to make worldwide "\ + "calls to regular and cell phones and send SMS messages.

"\ + "Choose what method you wish to use."), + tr("&Wizard"), tr("&Profile editor"), tr("&Diamondcard")); + + switch (newProfileMethod) { + case 0: + wizardProfile(true); + break; + case 1: + newProfile(true); + break; + case 2: + diamondcardProfile(true); + break; + default: + return QDialog::Rejected; + } + + if (profileListView->childCount() == 0) { + // No profile has been created. + return QDialog::Rejected; + } + + // Select the created profile + QCheckListItem *item = (QCheckListItem *)profileListView->currentItem(); + QString profile = item->text(); + profile.append(USER_FILE_EXT); + selectedProfiles.clear(); + selectedProfiles.push_back(profile.ascii()); + + 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, "system settings", 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(QMainWindow *_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); + QListViewItemIterator j(profileListView); + while (j.current()) { + QCheckListItem *item = (QCheckListItem *)j.current(); + 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.ascii()) != l.end()) + { + item->setPixmap(0, QPixmap::fromMimeSource("twinkle16.png")); + defaultSet = true; + } + + // Tick check box of active profile + if (phone->ref_user_profile(profile.ascii())) { + item->setOn(true); + } + + j++; + } + + sysPushButton->hide(); + runPushButton->setText("&OK"); + runPushButton->setFocus(); + QDialog::show(); +} + +void SelectProfileForm::runProfile() +{ + selectedProfiles.clear(); + QListViewItemIterator i(profileListView, QListViewItemIterator::Checked); + while (i.current()) { + QCheckListItem *item = (QCheckListItem *)i.current(); + QString profile =item->text(); + profile.append(USER_FILE_EXT); + selectedProfiles.push_back(profile.ascii()); + i++; + } + + 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() +{ + QCheckListItem *item = (QCheckListItem *)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.ascii()); + if (active_user) { + list user_list; + user_list.push_back(active_user); + UserProfileForm *f = new UserProfileForm(this, + "edit user profile", true, + Qt::WDestructiveClose); + + 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.ascii(), 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, "edit user profile", true, + Qt::WDestructiveClose); + f->show(user_list, ""); +} + +void SelectProfileForm::newProfile() +{ + newProfile(false); +} + +void SelectProfileForm::newProfile(bool exec_mode) +{ + // Ask user for a profile name + GetProfileNameForm getProfileNameForm(this, "get profile name", 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.ascii()); + + // Show the edit user profile form (modal dialog) + list user_list; + user_list.push_back(user_config); + UserProfileForm *f = new UserProfileForm(this, "edit user profile", true, + Qt::WDestructiveClose); + 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 + QCheckListItem *item = new QCheckListItem(profileListView, + user_config->get_profile_name().c_str(), + QCheckListItem::CheckBox); + item->setPixmap(0, QPixmap::fromMimeSource("penguin-small.png")); + + // 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->setSelected(item, true); + + // 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() +{ + QCheckListItem *item = (QCheckListItem *)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.ascii()) != l.end()) { + l.remove(profile.ascii()); + 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 + QCheckListItem *item = (QCheckListItem *)profileListView-> + currentItem(); + delete item; + if (profileListView->childCount() == 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->setSelected(profileListView-> + firstChild(), true); + } + } + } + + MEMMAN_DELETE(mb); + delete mb; +} + +void SelectProfileForm::renameProfile() +{ + QCheckListItem *item = (QCheckListItem *)profileListView->currentItem(); + QString oldProfile = item->text(); + + // Ask user for a new profile name + GetProfileNameForm getProfileNameForm(this, "get profile name", 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.ascii()) != l.end()) + { + std::replace(l.begin(), l.end(), oldProfile.ascii(), newProfile.ascii()); + 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 + QCheckListItem *item = (QCheckListItem *)profileListView->currentItem(); + item->setText(0, 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 + QListViewItemIterator i(profileListView); + while (i.current()) { + i.current()->setPixmap(0, QPixmap::fromMimeSource("penguin-small.png")); + i++; + } + + // Set pixmap of the default profiles. + // Set default profiles in system settings. + list l; + QListViewItemIterator j(profileListView, QListViewItemIterator::Checked); + while (j.current()) { + QCheckListItem *item = (QCheckListItem *)j.current(); + item->setPixmap(0, QPixmap::fromMimeSource("twinkle16.png")); + l.push_back(item->text().ascii()); + j++; + } + 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, "get profile name", 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.ascii()); + + // Show the wizard form (modal dialog) + WizardForm *f = new WizardForm(this, "wizard", true, Qt::WDestructiveClose); + 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, "diamondcard", + true, Qt::WDestructiveClose); + 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, "system settings", true, + Qt::WDestructiveClose); + 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.setNameFilter(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)); + QCheckListItem *item = new QCheckListItem( + profileListView, profile, QCheckListItem::CheckBox); + item->setPixmap(0, QPixmap::fromMimeSource("penguin-small.png")); + } + + // Highlight the first profile + profileListView->setSelected(profileListView->firstChild(), true); +} + +void SelectProfileForm::toggleItem(QListViewItem *item) +{ + QCheckListItem *checkItem = (QCheckListItem *)item; + checkItem->setOn(!checkItem->isOn()); +} + diff --git a/src/gui/selectuserform.ui b/src/gui/selectuserform.ui new file mode 100644 index 0000000..19a7ff7 --- /dev/null +++ b/src/gui/selectuserform.ui @@ -0,0 +1,206 @@ + +SelectUserForm + + + SelectUserForm + + + + 0 + 0 + 584 + 228 + + + + Twinkle - Select user + + + + unnamed + + + + cancelPushButton + + + &Cancel + + + Alt+C + + + + + selectPushButton + + + &Select all + + + Alt+S + + + + + spacer47 + + + Horizontal + + + Expanding + + + + 41 + 20 + + + + + + okPushButton + + + &OK + + + Alt+O + + + true + + + + + clearPushButton + + + C&lear all + + + Alt+L + + + + + layout100 + + + + unnamed + + + + purposeTextLabel + + + + 1 + 5 + 0 + 0 + + + + purpose + No need to translate + + + + + + User + + + true + + + true + + + + userListView + + + Manual + + + NoSelection + + + LastColumn + + + + + + + + + okPushButton + clicked() + SelectUserForm + validate() + + + cancelPushButton + clicked() + SelectUserForm + reject() + + + selectPushButton + clicked() + SelectUserForm + selectAll() + + + clearPushButton + clicked() + SelectUserForm + clearAll() + + + userListView + doubleClicked(QListViewItem*) + SelectUserForm + toggle(QListViewItem*) + + + + userListView + selectPushButton + clearPushButton + okPushButton + cancelPushButton + + + user.h + phone.h + gui.h + cassert + qlistview.h + selectuserform.ui.h + + + extern t_phone *phone; + + + selection(list<t_user *>) + not_selected(list<t_user*>) + + + show( t_select_purpose purpose ) + validate() + selectAll() + clearAll() + toggle( QListViewItem * item ) + + + init() + + + + diff --git a/src/gui/selectuserform.ui.h b/src/gui/selectuserform.ui.h new file mode 100644 index 0000000..3b2862d --- /dev/null +++ b/src/gui/selectuserform.ui.h @@ -0,0 +1,138 @@ +/**************************************************************************** +** 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +void SelectUserForm::init() +{ + // Disable sorting + userListView->setSorting(-1); +} + +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); + } + setCaption(title); + purposeTextLabel->setText(msg_purpose); + + // Fill list view + list user_list = phone->ref_users(); + for (list::reverse_iterator i = user_list.rbegin(); i != user_list.rend(); i++) { + QCheckListItem *item = new QCheckListItem(userListView, + (*i)->get_profile_name().c_str(), QCheckListItem::CheckBox); + + switch (purpose) { + case SELECT_DND: + item->setOn(phone->ref_service(*i)->is_dnd_active()); + break; + case SELECT_AUTO_ANSWER: + item->setOn(phone->ref_service(*i)->is_auto_answer_active()); + break; + default: + break; + } + } + + QDialog::show(); +} + +void SelectUserForm::validate() +{ + list selected_list, not_selected_list; + + QListViewItemIterator i(userListView); + while (i.current()) { + QCheckListItem *item = (QCheckListItem *)(i.current()); + if (item->isOn()) { + selected_list.push_back(phone-> + ref_user_profile(item->text().ascii())); + } else { + not_selected_list.push_back(phone-> + ref_user_profile(item->text().ascii())); + } + i++; + } + + emit (selection(selected_list)); + emit (not_selected(not_selected_list)); + accept(); +} + +void SelectUserForm::selectAll() +{ + QListViewItemIterator i(userListView); + while (i.current()) { + QCheckListItem *item = (QCheckListItem *)(i.current()); + item->setOn(true); + i++; + } +} + +void SelectUserForm::clearAll() +{ + QListViewItemIterator i(userListView); + while (i.current()) { + QCheckListItem *item = (QCheckListItem *)(i.current()); + item->setOn(false); + i++; + } +} + +void SelectUserForm::toggle(QListViewItem *item) +{ + QCheckListItem *checkItem = (QCheckListItem *)item; + checkItem->setOn(!checkItem->isOn()); +} diff --git a/src/gui/sendfileform.ui b/src/gui/sendfileform.ui new file mode 100644 index 0000000..748459b --- /dev/null +++ b/src/gui/sendfileform.ui @@ -0,0 +1,217 @@ + +SendFileForm + + + SendFileForm + + + + 0 + 0 + 461 + 127 + + + + Twinkle - Send File + + + + unnamed + + + + layout166 + + + + unnamed + + + + fileToolButton + + + TabFocus + + + + + + fileopen.png + + + Select file to send. + + + + + spacer62 + + + Horizontal + + + Minimum + + + + 28 + 20 + + + + + + fileTextLabel + + + &File: + + + fileLineEdit + + + + + subjectTextLabel + + + &Subject: + + + subjectLineEdit + + + + + fileLineEdit + + + + + subjectLineEdit + + + + + + + spacer66 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + layout59 + + + + unnamed + + + + spacer65 + + + Horizontal + + + Expanding + + + + 141 + 20 + + + + + + okPushButton + + + &OK + + + Alt+O + + + + + cancelPushButton + + + &Cancel + + + Alt+C + + + + + + + + + cancelPushButton + clicked() + SendFileForm + reject() + + + okPushButton + clicked() + SendFileForm + signalSelectedInfo() + + + fileToolButton + clicked() + SendFileForm + chooseFile() + + + + subjectLineEdit + fileLineEdit + okPushButton + cancelPushButton + + + qstring.h + audits/memman.h + gui.h + qfile.h + qfiledialog.h + sendfileform.ui.h + + + QDialog *_chooseFileDialog; + + + selected(const QString &filename, const QString &subject) + + + signalSelectedInfo() + chooseFile() + setFilename() + + + init() + destroy() + + + + diff --git a/src/gui/sendfileform.ui.h b/src/gui/sendfileform.ui.h new file mode 100644 index 0000000..067b6dd --- /dev/null +++ b/src/gui/sendfileform.ui.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifdef HAVE_KDE +#include +#endif + +void SendFileForm::init() +{ + setWFlags(getWFlags() | Qt::WDestructiveClose); + _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.").ascii(), 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(QString::null, QString::null, this, 0, true); + MEMMAN_NEW(d); + + connect(d, SIGNAL(fileSelected(const QString &)), this, SLOT(setFilename())); +#endif + d->setCaption(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); + filename = d->selectedFile(); +#endif + + fileLineEdit->setText(filename); +} diff --git a/src/gui/srvredirectform.ui b/src/gui/srvredirectform.ui new file mode 100644 index 0000000..6f0430e --- /dev/null +++ b/src/gui/srvredirectform.ui @@ -0,0 +1,819 @@ + +SrvRedirectForm + + + SrvRedirectForm + + + + 0 + 0 + 648 + 315 + + + + Twinkle - Call Redirection + + + + unnamed + + + + layout114 + + + + unnamed + + + + userTextLabel + + + User: + + + userComboBox + + + + + userComboBox + + + + 7 + 0 + 0 + 0 + + + + + + + + cfTabWidget + + + 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> + + + + tab + + + &Unconditional + + + + unnamed + + + + cfAlwaysCheckBox + + + &Redirect all calls + + + Alt+R + + + Activate the unconditional redirection service. + + + + + cfAlwaysGroupBox + + + true + + + Redirect to + + + + unnamed + + + + cfAlwaysDst1LineEdit + + + true + + + LineEditPanel + + + Sunken + + + 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. + + + + + cfAlwaysDst2LineEdit + + + 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. + + + + + cfAlwaysDst3TextLabel + + + true + + + &3rd choice destination: + + + cfAlwaysDst3LineEdit + + + + + cfAlwaysDst3LineEdit + + + 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. + + + + + cfAlwaysDst2TextLabel + + + &2nd choice destination: + + + cfAlwaysDst2LineEdit + + + + + cfAlwaysDst1TextLabel + + + &1st choice destination: + + + cfAlwaysDst1LineEdit + + + + + addrAlways1ToolButton + + + TabFocus + + + + + + F10 + + + kontact_contacts.png + + + Address book + + + + + addrAlways2ToolButton + + + TabFocus + + + + + + F11 + + + kontact_contacts.png + + + Address book + + + Select an address from the address book. + + + + + addrAlways3ToolButton + + + TabFocus + + + + + + F12 + + + kontact_contacts.png + + + Address book + + + Select an address from the address book. + + + + + + + + + tab + + + &Busy + + + + unnamed + + + + cfBusyCheckBox + + + &Redirect calls when I am busy + + + Alt+R + + + Activate the redirection when busy service. + + + + + cfBusyGroupBox + + + true + + + Redirect to + + + + unnamed + + + + cfBusyDst2LineEdit + + + 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. + + + + + cfBusyDst3TextLabel + + + &3rd choice destination: + + + cfAlwaysDst3LineEdit + + + + + cfBusyDst3LineEdit + + + 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. + + + + + cfBusyDst2TextLabel + + + &2nd choice destination: + + + cfAlwaysDst2LineEdit + + + + + cfBusyDst1TextLabel + + + &1st choice destination: + + + cfAlwaysDst1LineEdit + + + + + cfBusyDst1LineEdit + + + 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. + + + + + addrBusy1ToolButton + + + + + + kontact_contacts.png + + + Address book + + + Select an address from the address book. + + + + + addrBusy2ToolButton + + + + + + kontact_contacts.png + + + Address book + + + Select an address from the address book. + + + + + addrBusy3ToolButton + + + + + + kontact_contacts.png + + + Address book + + + Select an address from the address book. + + + + + + + + + tab + + + &No answer + + + + unnamed + + + + cfNoanswerCheckBox + + + &Redirect calls when I do not answer + + + Alt+R + + + Activate the redirection on no answer service. + + + + + cfNoanswerGroupBox + + + true + + + Redirect to + + + + unnamed + + + + cfNoanswerDst2LineEdit + + + 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. + + + + + cfNoanswerDst3TextLabel + + + &3rd choice destination: + + + cfAlwaysDst3LineEdit + + + + + cfNoanswerDst2TextLabel + + + &2nd choice destination: + + + cfAlwaysDst2LineEdit + + + + + cfNoanswerDst1TextLabel + + + &1st choice destination: + + + cfAlwaysDst1LineEdit + + + + + cfNoanswerDst1LineEdit + + + 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. + + + + + cfNoanswerDst3LineEdit + + + 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. + + + + + addrNoanswer1ToolButton + + + + + + kontact_contacts.png + + + Address book + + + Select an address from the address book. + + + + + addrNoanswer2ToolButton + + + + + + kontact_contacts.png + + + Address book + + + Select an address from the address book. + + + + + addrNoanswer3ToolButton + + + + + + kontact_contacts.png + + + Address book + + + Select an address from the address book. + + + + + + + + + + spacer14 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + layout23 + + + + unnamed + + + + spacer13 + + + Horizontal + + + Expanding + + + + 261 + 20 + + + + + + okPushButton + + + &OK + + + Alt+O + + + true + + + Accept and save all changes. + + + + + cancelPushButton + + + &Cancel + + + Alt+C + + + Undo your changes and close the window. + + + + + + + + + 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(const QString&) + SrvRedirectForm + changedUser(const QString&) + + + + cfTabWidget + cfAlwaysCheckBox + cfAlwaysDst1LineEdit + cfAlwaysDst2LineEdit + cfAlwaysDst3LineEdit + cfBusyCheckBox + cfBusyDst1LineEdit + cfBusyDst2LineEdit + cfBusyDst3LineEdit + cfNoanswerCheckBox + cfNoanswerDst1LineEdit + cfNoanswerDst2LineEdit + cfNoanswerDst3LineEdit + okPushButton + cancelPushButton + + + phone.h + qgroupbox.h + qcheckbox.h + gui.h + audits/memman.h + sockets/url.h + list + qlineedit.h + getaddressform.h + user.h + phone.h + srvredirectform.ui.h + + + extern t_phone *phone; + + + int nrAddressBook; + GetAddressForm *getAddressForm; + t_user *current_user; + int current_user_idx; + + + destinations(t_user *, const list<t_display_url> &always, const list<t_display_url> &busy, const list<t_display_url> &noanswer) + + + show() + populate() + validate() + toggleAlways( bool on ) + toggleBusy( bool on ) + toggleNoanswer( bool on ) + changedUser( const QString & user_display_uri ) + showAddressBook() + showAddressBook1() + showAddressBook2() + showAddressBook3() + showAddressBook4() + showAddressBook5() + showAddressBook6() + showAddressBook7() + showAddressBook8() + showAddressBook9() + selectedAddress( const QString & address ) + + + init() + destroy() + validateValues() + validate( bool cf_active, QLineEdit * dst1, QLineEdit * dst2, QLineEdit * dst3, list<t_display_url> & dest_list ) + + + + diff --git a/src/gui/srvredirectform.ui.h b/src/gui/srvredirectform.ui.h new file mode 100644 index 0000000..ecbadb7 --- /dev/null +++ b/src/gui/srvredirectform.ui.h @@ -0,0 +1,389 @@ +/**************************************************************************** +** 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, preserving your code. Create 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +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. + QIconSet i; + i = addrAlways1ToolButton->iconSet(); + i.setPixmap(QPixmap::fromMimeSource("kontact_contacts-disabled.png"), + QIconSet::Automatic, QIconSet::Disabled); + addrAlways1ToolButton->setIconSet(i); + addrAlways2ToolButton->setIconSet(i); + addrAlways3ToolButton->setIconSet(i); + addrBusy1ToolButton->setIconSet(i); + addrBusy2ToolButton->setIconSet(i); + addrBusy3ToolButton->setIconSet(i); + addrNoanswer1ToolButton->setIconSet(i); + addrNoanswer2ToolButton->setIconSet(i); + addrNoanswer3ToolButton->setIconSet(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.").ascii(), + 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->setCurrentPage(0); + return false; + } + + // Redirect busy + valid = validate(cfBusyCheckBox->isChecked(), + cfBusyDst1LineEdit, cfBusyDst2LineEdit, cfBusyDst3LineEdit, + cfDestBusy); + if (!valid) { + cfTabWidget->setCurrentPage(1); + return false; + } + + // Redirect no answer + valid = validate(cfNoanswerCheckBox->isChecked(), + cfNoanswerDst1LineEdit, cfNoanswerDst2LineEdit, + cfNoanswerDst3LineEdit, + cfDestNoanswer); + if (!valid) { + cfTabWidget->setCurrentPage(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().stripWhiteSpace().ascii(), 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().stripWhiteSpace().ascii(), 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().stripWhiteSpace().ascii(), 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.ascii()); + if (!new_user) { + userComboBox->setCurrentItem(current_user_idx); + return; + } + + if (!validateValues()) { + userComboBox->setCurrentItem(current_user_idx); + ((t_gui *)ui)->cb_show_msg(this, + tr("You have entered an invalid destination.").ascii(), + MSG_WARNING); + return; + } + + // Change current user + current_user_idx = userComboBox->currentItem(); + current_user = new_user; + populate(); +} + +void SrvRedirectForm::showAddressBook() +{ + if (!getAddressForm) { + getAddressForm = new GetAddressForm( + this, "select address", 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; + } +} diff --git a/src/gui/syssettingsform.ui b/src/gui/syssettingsform.ui new file mode 100644 index 0000000..300bf89 --- /dev/null +++ b/src/gui/syssettingsform.ui @@ -0,0 +1,1854 @@ + +SysSettingsForm + + + SysSettingsForm + + + + 0 + 0 + 765 + 624 + + + + Twinkle - System Settings + + + + unnamed + + + + + General + + + twinkle32.png + + + + + Audio + + + kmix.png + + + + + Ring tones + + + knotify.png + + + + + Address book + + + kontact_contacts32.png + + + + + Network + + + network.png + + + + + Log + + + log.png + + + + categoryListBox + + + + 1 + 7 + 0 + 0 + + + + Select a category for which you want to see or modify the settings. + + + + + layout38 + + + + unnamed + + + + spacer98 + + + Horizontal + + + Expanding + + + + 321 + 20 + + + + + + okPushButton + + + &OK + + + Alt+O + + + true + + + Accept and save your changes. + + + + + cancelPushButton + + + &Cancel + + + Alt+C + + + Undo all your changes and close the window. + + + + + + + settingsWidgetStack + + + + 7 + 5 + 0 + 0 + + + + Box + + + + pageAudio + + + 0 + + + + unnamed + + + + audioTitleTextLabel + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + Audio + + + 10 + + + + + soundcardGroupBox + + + Sound Card + + + + unnamed + + + + ringtoneComboBox + + + + 7 + 0 + 0 + 0 + + + + Select the sound card for playing the ring tone for incoming calls. + + + + + micComboBox + + + + 7 + 0 + 0 + 0 + + + + Select the sound card to which your microphone is connected. + + + + + speakerComboBox + + + + 7 + 0 + 0 + 0 + + + + Select the sound card for the speaker function during a call. + + + + + speakerTextLabel + + + &Speaker: + + + speakerComboBox + + + + + ringtoneTextLabel + + + &Ring tone: + + + ringtoneComboBox + + + + + otherRingtoneTextLabel + + + Other device: + + + otherRingtoneLineEdit + + + + + otherSpeakerTextLabel + + + Other device: + + + otherSpeakerLineEdit + + + + + otherMicTextLabel + + + Other device: + + + otherMicLineEdit + + + + + micTextLabel + + + &Microphone: + + + micComboBox + + + + + otherRingtoneLineEdit + + + + + otherMicLineEdit + + + + + otherSpeakerLineEdit + + + + + validateAudioCheckBox + + + &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. + + + + + + + advancedSoundGroupBox + + + Advanced + + + + unnamed + + + + layout31 + + + + unnamed + + + + ossFragmnetTextLabel + + + OSS &fragment size: + + + ossFragmentComboBox + + + + + + 16 + + + + + 32 + + + + + 64 + + + + + 128 + + + + + 256 + + + + alsaPlayPeriodComboBox + + + 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. + + + + + alsaPlayPeriodTextLabel + + + ALSA &play period size: + + + alsaPlayPeriodComboBox + + + + + alsaCapturePeriosTextLabel + + + &ALSA capture period size: + + + alsaCapturePeriodComboBox + + + + + + 16 + + + + + 32 + + + + + 64 + + + + + 128 + + + + + 256 + + + + ossFragmentComboBox + + + 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 + + + + alsaCapturePeriodComboBox + + + 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. + + + + + + + spacer27 + + + Horizontal + + + Expanding + + + + 121 + 20 + + + + + + + + spacer97 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + + + pageLog + + + 1 + + + + unnamed + + + + logTitleTextLabel + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + Log + + + 10 + + + + + layout8 + + + + unnamed + + + + logMaxSizeTextLabel + + + &Max log size: + + + logMaxSizeSpinBox + + + + + logMaxSizeSpinBox + + + 100 + + + 1 + + + 5 + + + 5 + + + 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. + + + + + logSizeMbTextLabel + + + MB + + + + + spacer7 + + + Horizontal + + + Expanding + + + + 211 + 20 + + + + + + + + logDebugCheckBox + + + Log &debug reports + + + Alt+D + + + Indicates if reports marked as "debug" will be logged. + + + + + logSipCheckBox + + + Log &SIP reports + + + Alt+S + + + Indicates if SIP messages will be logged. + + + + + logStunCheckBox + + + Log S&TUN reports + + + Alt+T + + + Indicates if STUN messages will be logged. + + + + + logMemoryCheckBox + + + Log m&emory reports + + + Alt+E + + + Indicates if reports concerning memory management will be logged. + + + + + spacer6 + + + Vertical + + + Expanding + + + + 20 + 61 + + + + + + + + pageGeneral + + + 2 + + + + unnamed + + + + generalTitleTextLabel + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + General + + + 10 + + + + + systrayGroupBox + + + System tray + + + + unnamed + + + + guiUseSystrayCheckBox + + + Create &system tray icon on startup + + + Alt+S + + + Enable this option if you want a system tray icon for Twinkle. The system tray icon is created when you start Twinkle. + + + + + guiHideCheckBox + + + &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. + + + + + + + startupGroupBox + + + Startup + + + + unnamed + + + + startHiddenCheckBox + + + S&tartup hidden in system tray + + + Alt+T + + + 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 + + + true + + + true + + + + profileListView + + + NoSelection + + + LastColumn + + + 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. + + + + + + + srvGroupBox + + + Services + + + + unnamed + + + + callWaitingCheckBox + + + 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. + + + + + hangupBothCheckBox + + + 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. + + + + + + + layout23 + + + + unnamed + + + + histSizeTextLabel + + + &Maximum calls in call history: + + + histSizeSpinBox + + + + + histSizeSpinBox + + + 1000 + + + 10 + + + The maximum number of calls that will be kept in the call history. + + + + + spacer73 + + + Horizontal + + + Expanding + + + + 191 + 20 + + + + + + + + layout21 + + + + unnamed + + + + autoShowCheckBox + + + &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. + + + + + autoShowTimeoutSpinBox + + + 60 + + + Number of seconds after which the main window should be shown. + + + + + secAutoShowTextLabel + + + secs + + + + + spacer14 + + + Horizontal + + + Expanding + + + + 29 + 20 + + + + + + + + layout34 + + + + unnamed + + + + browserTextLabel + + + W&eb browser command: + + + browserLineEdit + + + + + browserLineEdit + + + Command to start your web browser. If you leave this field empty Twinkle will try to figure out your default web browser. + + + + + + + + + pageNetwork + + + 3 + + + + unnamed + + + + networkTitleTextLabel + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + Network + + + 10 + + + + + spacer9 + + + Vertical + + + Expanding + + + + 20 + 230 + + + + + + spacer8 + + + Horizontal + + + Expanding + + + + 314 + 20 + + + + + + maxUdpSizeLineEdit + + + Maximum allowed size (0-65535) in bytes of an incoming SIP message over UDP. + + + + + sipUdpPortTextLabel + + + &SIP port: + + + sipUdpPortSpinBox + + + + + rtpPortTextLabel + + + &RTP port: + + + rtpPortSpinBox + + + + + maxTcpSizeTextLabel + + + Max. SIP message size (&TCP): + + + maxTcpSizeLineEdit + + + + + spacer7_2 + + + Horizontal + + + Expanding + + + + 314 + 20 + + + + + + sipUdpPortSpinBox + + + 65535 + + + 1025 + + + 5060 + + + The UDP/TCP port used for sending and receiving SIP messages. + + + + + maxUdpSizeTextLabel + + + Max. SIP message size (&UDP): + + + maxUdpSizeLineEdit + + + + + maxTcpSizeLineEdit + + + Maximum allowed size (0-4294967295) in bytes of an incoming SIP message over TCP. + + + + + rtpPortSpinBox + + + 65535 + + + 1025 + + + 2 + + + 8000 + + + 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. + + + + + + + pageRingtones + + + 4 + + + + unnamed + + + + ringtonesTitleTextLabel + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + Ring tones + + + 10 + + + + + ringtoneButtonGroup + + + Ring tone + + + + unnamed + + + + playRingtoneCheckBox + + + &Play ring tone on incoming call + + + Alt+P + + + Indicates if a ring tone should be played when a call comes in. + + + + + defaultRingtoneRadioButton + + + &Default ring tone + + + Alt+D + + + true + + + Play the default ring tone when a call comes in. + + + + + customRingtoneRadioButton + + + C&ustom ring tone + + + Alt+U + + + Play a custom ring tone when a call comes in. + + + + + layout16 + + + + unnamed + + + + spacer21 + + + Horizontal + + + Fixed + + + + 20 + 20 + + + + + + ringtoneLineEdit + + + Specify the file name of a .wav file that you want to be played as ring tone. + + + + + openRingtoneToolButton + + + TabFocus + + + + + + fileopen.png + + + Select ring tone file. + + + + + + + + + ringbackButtonGroup + + + Ring back tone + + + + unnamed + + + + playRingbackCheckBox + + + 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> + + + + + defaultRingbackRadioButton + + + D&efault ring back tone + + + Alt+E + + + true + + + Play the default ring back tone. + + + + + customRingbackRadioButton + + + Cu&stom ring back tone + + + Alt+S + + + Play a custom ring back tone. + + + + + layout16_2 + + + + unnamed + + + + spacer21_2 + + + Horizontal + + + Fixed + + + + 20 + 20 + + + + + + ringbackLineEdit + + + Specify the file name of a .wav file that you want to be played as ring back tone. + + + + + openRingbackToolButton + + + TabFocus + + + + + + fileopen.png + + + Select ring back tone file. + + + + + + + + + spacer23 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + + + pageAddressBook + + + 5 + + + + unnamed + + + + ringtonesTitleTextLabel_2 + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + Address book + + + 10 + + + + + abLookupNameCheckBox + + + &Lookup name for incoming call + + + Alt+L + + + 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. + + + + + abOverrideDisplayCheckBox + + + 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. + + + + + abLookupPhotoCheckBox + + + Lookup &photo for incoming call + + + Alt+P + + + Lookup the photo of a caller in your address book and display it on an incoming call. + + + + + spacer61 + + + Vertical + + + Expanding + + + + 20 + 121 + + + + + + + + + + + okPushButton + clicked() + SysSettingsForm + validate() + + + cancelPushButton + clicked() + SysSettingsForm + reject() + + + categoryListBox + highlighted(int) + SysSettingsForm + showCategory(int) + + + guiUseSystrayCheckBox + toggled(bool) + guiHideCheckBox + setEnabled(bool) + + + guiUseSystrayCheckBox + toggled(bool) + guiHideCheckBox + setChecked(bool) + + + guiUseSystrayCheckBox + toggled(bool) + startHiddenCheckBox + setEnabled(bool) + + + playRingtoneCheckBox + toggled(bool) + customRingtoneRadioButton + setEnabled(bool) + + + playRingtoneCheckBox + toggled(bool) + defaultRingtoneRadioButton + setEnabled(bool) + + + playRingtoneCheckBox + toggled(bool) + SysSettingsForm + playRingToneCheckBoxToggles(bool) + + + playRingtoneCheckBox + toggled(bool) + openRingtoneToolButton + setEnabled(bool) + + + playRingbackCheckBox + toggled(bool) + customRingbackRadioButton + setEnabled(bool) + + + playRingbackCheckBox + toggled(bool) + defaultRingbackRadioButton + setEnabled(bool) + + + playRingbackCheckBox + toggled(bool) + SysSettingsForm + playRingBackToneCheckBoxToggles(bool) + + + playRingbackCheckBox + toggled(bool) + openRingbackToolButton + setEnabled(bool) + + + openRingtoneToolButton + clicked() + SysSettingsForm + chooseRingtone() + + + openRingbackToolButton + clicked() + SysSettingsForm + chooseRingback() + + + customRingtoneRadioButton + toggled(bool) + ringtoneLineEdit + setEnabled(bool) + + + customRingtoneRadioButton + toggled(bool) + openRingtoneToolButton + setEnabled(bool) + + + customRingbackRadioButton + toggled(bool) + ringbackLineEdit + setEnabled(bool) + + + customRingbackRadioButton + toggled(bool) + openRingbackToolButton + setEnabled(bool) + + + abLookupNameCheckBox + toggled(bool) + abOverrideDisplayCheckBox + setEnabled(bool) + + + ringtoneComboBox + activated(int) + SysSettingsForm + devRingtoneSelected(int) + + + speakerComboBox + activated(int) + SysSettingsForm + devSpeakerSelected(int) + + + micComboBox + activated(int) + SysSettingsForm + devMicSelected(int) + + + + 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 + qlistbox.h + qcombobox.h + gui.h + sockets/interfaces.h + selectprofileform.h + qstringlist.h + audits/memman.h + qlistview.h + qspinbox.h + qfiledialog.h + qfileinfo.h + twinkle_config.h + qregexp.h + qvalidator.h + syssettingsform.ui.h + + + int idxOtherCaptureDevOss; + int idxOtherCaptureDevAlsa; + int idxOtherPlaybackDevOss; + int idxOtherPlaybackDevAlsa; + list<t_audio_device> list_audio_playback_dev; + list<t_audio_device> list_audio_capture_dev; + + + sipUdpPortChanged() + rtpPortChanged() + + + showCategory( int index ) + populateComboBox( QComboBox * cb, const QString & s ) + populate() + validate() + show() + exec() + chooseRingtone() + chooseRingback() + devRingtoneSelected( int idx ) + devSpeakerSelected( int idx ) + devMicSelected( int idx ) + playRingToneCheckBoxToggles( bool on ) + playRingBackToneCheckBoxToggles( bool on ) + + + init() + comboItem2audio_dev( QString item, QLineEdit * qleOther, bool playback ) + + + + diff --git a/src/gui/syssettingsform.ui.h b/src/gui/syssettingsform.ui.h new file mode 100644 index 0000000..1e9afda --- /dev/null +++ b/src/gui/syssettingsform.ui.h @@ -0,0 +1,489 @@ +/**************************************************************************** +** 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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. + QIconSet i; + i = openRingtoneToolButton->iconSet(); + i.setPixmap(QPixmap::fromMimeSource("fileopen-disabled.png"), + QIconSet::Automatic, QIconSet::Disabled); + openRingtoneToolButton->setIconSet(i); + i = openRingbackToolButton->iconSet(); + i.setPixmap(QPixmap::fromMimeSource("fileopen-disabled.png"), + QIconSet::Automatic, QIconSet::Disabled); + openRingbackToolButton->setIconSet(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->raiseWidget(pageGeneral); + } else if (index == idxCatAudio) { + settingsWidgetStack->raiseWidget(pageAudio); + } else if (index == idxCatRingtones) { + settingsWidgetStack->raiseWidget(pageRingtones); + } else if (index == idxCatAddressBook) { + settingsWidgetStack->raiseWidget(pageAddressBook); + } else if (index == idxCatNetwork) { + settingsWidgetStack->raiseWidget(pageNetwork); + } else if (index == idxCatLog) { + settingsWidgetStack->raiseWidget(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()).ascii(); + } + + if (item == QString("OSS: ") + DEV_OTHER) { + if (qleOther->text().isEmpty()) return ""; + return (QString(PFX_OSS) + qleOther->text()).ascii(); + } + + 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.ascii()) { + return i->get_settings_value(); + } + } + + return ""; +} + +void SysSettingsForm::populateComboBox(QComboBox *cb, const QString &s) +{ + for (int i = 0; i < cb->count(); i++) { + if (cb->text(i) == s) { + cb->setCurrentItem(i); + return; + } + } +} + +void SysSettingsForm::populate() +{ + QString msg; + int idx; + + // Select the Audio category + categoryListBox->setSelected(idxCatGeneral, true); + settingsWidgetStack->raiseWidget(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->insertItem(QString(item.c_str())); + speakerComboBox->insertItem(QString(item.c_str())); + + // Select audio device + if (sys_config->get_dev_ringtone().device == i->device) { + ringtoneComboBox->setCurrentItem(idx); + otherRingtoneLineEdit->clear(); + devRingtoneFound = true; + } + if (sys_config->get_dev_speaker().device == i->device) { + speakerComboBox->setCurrentItem(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->setCurrentItem( + (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->setCurrentItem( + (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->insertItem(QString(item.c_str())); + + // Select audio device + if (sys_config->get_dev_mic().device == i->device) { + micComboBox->setCurrentItem(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->setCurrentItem( + (dev.type == t_audio_device::ALSA ? idxOtherCaptureDevAlsa : idxOtherCaptureDevOss)); + } + + // Enable/disable line edit for non-standard device + devRingtoneSelected(ringtoneComboBox->currentItem()); + devSpeakerSelected(speakerComboBox->currentItem()); + devMicSelected(micComboBox->currentItem()); + + 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()); + + QStringList profiles; + if (!SelectProfileForm::getUserProfiles(profiles, msg)) { + ((t_gui *)ui)->cb_show_msg(this, msg.ascii(), 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); + QCheckListItem *item = new QCheckListItem(profileListView, + profile, QCheckListItem::CheckBox); + item->setPixmap(0, QPixmap::fromMimeSource("penguin-small.png")); + + list l = sys_config->get_start_user_profiles(); + if (std::find(l.begin(), l.end(), profile.ascii()) != l.end()) + { + item->setOn(true); + } + } + + // 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()); + + // 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; + QListViewItemIterator i(profileListView, QListViewItemIterator::Checked); + while (i.current()) { + QCheckListItem *item = (QCheckListItem *)i.current(); + start_user_profiles.push_back(item->text().ascii()); + i++; + } + sys_config->set_start_user_profiles(start_user_profiles); + + // Web browser command + sys_config->set_gui_browser_cmd(browserLineEdit->text().stripWhiteSpace().ascii()); + + // 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->isOn()) { + sys_config->set_ringtone_file(""); + } else { + sys_config->set_ringtone_file(ringtoneLineEdit-> + text().stripWhiteSpace().ascii()); + } + } else { + sys_config->set_ringtone_file(""); + } + + sys_config->set_play_ringback(playRingbackCheckBox->isChecked()); + if (sys_config->get_play_ringback()) { + if (defaultRingbackRadioButton->isOn()) { + sys_config->set_ringback_file(""); + } else { + sys_config->set_ringback_file(ringbackLineEdit-> + text().stripWhiteSpace().ascii()); + } + } 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( + ((t_gui *)ui)->get_last_file_browse_path(), + tr("Ring tones", "Description of .wav files in file dialog").append(" (*.wav)"), this, "ring tone file dialog", + tr("Choose ring tone")); + if (!file.isEmpty()) { + ringtoneLineEdit->setText(file); + ((t_gui *)ui)->set_last_file_browse_path(QFileInfo(file).dirPath(true)); + } +} + +void SysSettingsForm::chooseRingback() +{ + QString file = QFileDialog::getOpenFileName( + ((t_gui *)ui)->get_last_file_browse_path(), + tr("Ring back tones", "Description of .wav files in file dialog").append(" (*.wav)"), this, "ring back file dialog", + tr("Choose ring back tone")); + if (!file.isEmpty()) { + ringbackLineEdit->setText(file); + ((t_gui *)ui)->set_last_file_browse_path(QFileInfo(file).dirPath(true)); + } +} + +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); + } +} diff --git a/src/gui/termcapform.ui b/src/gui/termcapform.ui new file mode 100644 index 0000000..08f72d0 --- /dev/null +++ b/src/gui/termcapform.ui @@ -0,0 +1,241 @@ + +TermCapForm + + + TermCapForm + + + + 0 + 0 + 581 + 168 + + + + + 5 + 5 + 0 + 0 + + + + Twinkle - Terminal Capabilities + + + + unnamed + + + + layout53 + + + + unnamed + + + + fromTextLabel + + + &From: + + + fromComboBox + + + + + fromComboBox + + + + 7 + 0 + 0 + 0 + + + + + + + + termCapGroupBox + + + Get terminal capabilities of + + + + unnamed + + + + partyTextLabel + + + &To: + + + partyLineEdit + + + + + partyLineEdit + + + 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. + + + + + addressToolButton + + + TabFocus + + + + + + F10 + + + kontact_contacts.png + + + Address book + + + Select an address from the address book. + + + + + + + spacer13 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + layout15 + + + + unnamed + + + + spacer12 + + + Horizontal + + + Expanding + + + + 131 + 20 + + + + + + okPushButton + + + &OK + + + true + + + + + cancelPushButton + + + &Cancel + + + + + + + + + cancelPushButton + clicked() + TermCapForm + reject() + + + okPushButton + clicked() + TermCapForm + validate() + + + addressToolButton + clicked() + TermCapForm + showAddressBook() + + + + partyLineEdit + addressToolButton + okPushButton + cancelPushButton + fromComboBox + + + gui.h + audits/memman.h + sockets/url.h + getaddressform.h + user.h + phone.h + termcapform.ui.h + + + extern t_phone *phone; + + + GetAddressForm *getAddressForm; + + + destination(t_user *, const t_url &) + + + show( t_user * user_config, const QString & dest ) + validate() + showAddressBook() + selectedAddress( const QString & address ) + + + init() + destroy() + + + + diff --git a/src/gui/termcapform.ui.h b/src/gui/termcapform.ui.h new file mode 100644 index 0000000..e04af11 --- /dev/null +++ b/src/gui/termcapform.ui.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** 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, preserving your code. Create 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +void TermCapForm::init() +{ + getAddressForm = 0; + + // Set toolbutton icons for disabled options. + setDisabledIcon(addressToolButton, "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->text(i) == + user_config->get_profile_name().c_str()) + { + fromComboBox->setCurrentItem(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().ascii()); + + ui->expand_destination(from_user, + partyLineEdit->text().stripWhiteSpace().ascii(), + 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, "select address", 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); +} diff --git a/src/gui/textbrowsernoautolink.h b/src/gui/textbrowsernoautolink.h new file mode 100644 index 0000000..8b45c4d --- /dev/null +++ b/src/gui/textbrowsernoautolink.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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, const char * name = 0 ) : + QTextBrowser(parent, name) {}; + virtual void setSource ( const QString & name ) {}; +}; + +#endif diff --git a/src/gui/transferform.ui b/src/gui/transferform.ui new file mode 100644 index 0000000..dc6b14e --- /dev/null +++ b/src/gui/transferform.ui @@ -0,0 +1,270 @@ + +TransferForm + + + TransferForm + + + + 0 + 0 + 532 + 251 + + + + Twinkle - Transfer + + + + unnamed + + + + transferGroupBox + + + Transfer call to + + + + unnamed + + + + toLabel + + + &To: + + + toLineEdit + + + + + 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. + + + + + addressToolButton + + + TabFocus + + + + + + F10 + + + kontact_contacts.png + + + Address book + + + Select an address from the address book. + + + + + + + typeButtonGroup2 + + + Type of transfer + + + + unnamed + + + + basicRadioButton + + + &Blind transfer + + + Alt+B + + + true + + + Transfer the call to a third party without contacting that third party yourself. + + + + + consultRadioButton + + + T&ransfer with consultation + + + Alt+R + + + Before transferring the call to a third party, first consult the party yourself. + + + + + otherLineRadioButton + + + Transfer to other &line + + + Alt+L + + + Connect the remote party on the active line with the remote party on the other line. + + + + + + + spacer24 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + layout28 + + + + unnamed + + + + spacer23 + + + Horizontal + + + Expanding + + + + 121 + 20 + + + + + + okPushButton + + + &OK + + + Alt+O + + + true + + + + + cancelPushButton + + + &Cancel + + + + + + + + + okPushButton + clicked() + TransferForm + validate() + + + cancelPushButton + clicked() + TransferForm + reject() + + + addressToolButton + clicked() + TransferForm + showAddressBook() + + + otherLineRadioButton + toggled(bool) + TransferForm + setOtherLineAddress(bool) + + + + gui.h + audits/memman.h + qstring.h + sockets/url.h + getaddressform.h + user.h + protocol.h + phone.h + transferform.ui.h + + + extern t_phone *phone + + + int consult_line; + t_user *user_config; + GetAddressForm *getAddressForm; + QString previousAddress; + + + destination(const t_display_url&, t_transfer_type) + + + initTransferOptions() + show( t_user * user ) + show( t_user * user, const string & dest, t_transfer_type transfer_type ) + hide() + reject() + validate() + closeEvent( QCloseEvent * ) + showAddressBook() + selectedAddress( const QString & address ) + setOtherLineAddress( bool on ) + + + init() + destroy() + + + + diff --git a/src/gui/transferform.ui.h b/src/gui/transferform.ui.h new file mode 100644 index 0000000..a5cdb83 --- /dev/null +++ b/src/gui/transferform.ui.h @@ -0,0 +1,195 @@ +/**************************************************************************** +** 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, preserving your code. Create 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +void TransferForm::init() +{ + getAddressForm = 0; + + // Set toolbutton icons for disabled options. + QIconSet i; + i = addressToolButton->iconSet(); + i.setPixmap(QPixmap::fromMimeSource("kontact_contacts-disabled.png"), + QIconSet::Automatic, QIconSet::Disabled); + addressToolButton->setIconSet(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().stripWhiteSpace().ascii(), dest); + + t_transfer_type transfer_type; + if (consultRadioButton->isOn()) { + transfer_type = TRANSFER_CONSULT; + } else if (otherLineRadioButton->isOn()) { + 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, "select address", 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 + } +} diff --git a/src/gui/twinkle.pro b/src/gui/twinkle.pro new file mode 100644 index 0000000..feaf6da --- /dev/null +++ b/src/gui/twinkle.pro @@ -0,0 +1,237 @@ +TEMPLATE = app +LANGUAGE = C++ + +CONFIG += qt warn_on release thread + +LIBS += ../libtwinkle.a ../parser/libsipparser.a ../sdp/libsdpparser.a ../sockets/libsocket.a ../threads/libthread.a ../audio/libaudio.a ../audits/libaudits.a ../stun/libstun.a ../mwi/libmwi.a ../im/libim.a ../patterns/libpatterns.a ../presence/libpresence.a ../utils/libutils.a -lsndfile -lmagic -lncurses -lreadline + +DEFINES += QT_NO_STL + +INCLUDEPATH += .. + +HEADERS += gui.h \ + historylistview.h \ + freedesksystray.h \ + twinklesystray.h \ + address_finder.h \ + qt_translator.h \ + core_strings.h \ + addresslistviewitem.h \ + yesnodialog.h \ + command_args.h \ + messageformview.h \ + buddylistview.h \ + textbrowsernoautolink.h \ + twinkleapplication.h + +SOURCES += main.cpp \ + gui.cpp \ + historylistview.cpp \ + freedesksystray.cpp \ + twinklesystray.cpp \ + address_finder.cpp \ + addresslistviewitem.cpp \ + yesnodialog.cpp \ + messageformview.cpp \ + buddylistview.cpp \ + twinkleapplication.cpp + +FORMS = mphoneform.ui \ + inviteform.ui \ + deregisterform.ui \ + redirectform.ui \ + termcapform.ui \ + dtmfform.ui \ + selectnicform.ui \ + srvredirectform.ui \ + authenticationform.ui \ + userprofileform.ui \ + selectprofileform.ui \ + getprofilenameform.ui \ + transferform.ui \ + syssettingsform.ui \ + logviewform.ui \ + wizardform.ui \ + getaddressform.ui \ + historyform.ui \ + selectuserform.ui \ + numberconversionform.ui \ + addresscardform.ui \ + messageform.ui \ + buddyform.ui \ + sendfileform.ui \ + diamondcardprofileform.ui + +IMAGES = images/filenew \ + images/filesave \ + images/print \ + images/undo \ + images/redo \ + images/editcut \ + images/editcopy \ + images/editpaste \ + images/searchfind \ + images/invite.png \ + images/answer.png \ + images/bye.png \ + images/reject.png \ + images/redirect.png \ + images/hold.png \ + images/dtmf.png \ + images/bye-disabled.png \ + images/redial.png \ + images/redial-disabled.png \ + images/invite-disabled.png \ + images/answer-disabled.png \ + images/reject-disabled.png \ + images/redirect-disabled.png \ + images/hold-disabled.png \ + images/dtmf-disabled.png \ + images/penguin.png \ + images/package_network.png \ + images/kmix.png \ + images/package_system.png \ + images/yast_babelfish.png \ + images/clock.png \ + images/yast_PhoneTTOffhook.png \ + images/penguin_big.png \ + images/password.png \ + images/kcmpci.png \ + images/penguin-small.png \ + images/conf.png \ + images/conf-disabled.png \ + images/mute.png \ + images/mute-disabled.png \ + images/twinkle16.png \ + images/twinkle48.png \ + images/twinkle32.png \ + images/transfer-disabled.png \ + images/transfer.png \ + images/log.png \ + images/dtmf-2.png \ + images/dtmf-3.png \ + images/dtmf-5.png \ + images/dtmf-6.png \ + images/dtmf-7.png \ + images/dtmf-8.png \ + images/dtmf-9.png \ + images/dtmf-4.png \ + images/dtmf-1.png \ + images/dtmf-0.png \ + images/dtmf-star.png \ + images/dtmf-pound.png \ + images/dtmf-a.png \ + images/dtmf-b.png \ + images/dtmf-c.png \ + images/dtmf-d.png \ + images/twinkle24.png \ + images/exit.png \ + images/kontact_contacts.png \ + images/ok.png \ + images/cancel.png \ + images/1rightarrow.png \ + images/1leftarrow-yellow.png \ + images/editdelete.png \ + images/kcmpci16.png \ + images/kontact_contacts-disabled.png \ + images/sys_auto_ans.png \ + images/sys_auto_ans_dis.png \ + images/sys_busy_estab.png \ + images/sys_busy_estab_dis.png \ + images/sys_busy_trans.png \ + images/sys_busy_trans_dis.png \ + images/sys_dnd.png \ + images/sys_dnd_dis.png \ + images/sys_idle.png \ + images/sys_idle_dis.png \ + images/sys_redir.png \ + images/sys_redir_dis.png \ + images/sys_services.png \ + images/sys_services_dis.png \ + images/sys_hold.png \ + images/sys_hold_dis.png \ + images/sys_mute.png \ + images/sys_mute_dis.png \ + images/network.png \ + images/knotify.png \ + images/fileopen.png \ + images/fileopen-disabled.png \ + images/cf.png \ + images/auto_answer.png \ + images/auto_answer-disabled.png \ + images/cancel-disabled.png \ + images/cf-disabled.png \ + images/missed-disabled.png \ + images/missed.png \ + images/sys_missed.png \ + images/sys_missed_dis.png \ + images/twinkle16-disabled.png \ + images/gear.png \ + images/reg_failed-disabled.png \ + images/reg_failed.png \ + images/no-indication.png \ + images/contexthelp.png \ + images/settings.png \ + images/reg-query.png \ + images/log_small.png \ + images/qt-logo.png \ + images/1leftarrow.png \ + images/1uparrow.png \ + images/1downarrow.png \ + images/kontact_contacts32.png \ + images/encrypted.png \ + images/sys_encrypted.png \ + images/sys_encrypted_dis.png \ + images/encrypted32.png \ + images/encrypted-disabled.png \ + images/stat_conference.png \ + images/stat_established.png \ + images/stat_outgoing.png \ + images/stat_ringing.png \ + images/stat_mute.png \ + images/stat_established_nomedia.png \ + images/encrypted_verified.png \ + images/sys_encrypted_verified.png \ + images/sys_encrypted_verified_dis.png \ + images/consult-xfer.png \ + images/mwi_new16.png \ + images/mwi_none16.png \ + images/mwi_none16_dis.png \ + images/sys_mwi.png \ + images/sys_mwi_dis.png \ + images/mwi_none.png \ + images/mwi_failure16.png \ + images/presence_offline.png \ + images/presence_online.png \ + images/presence_failed.png \ + images/presence_rejected.png \ + images/presence_unknown.png \ + images/edit16.png \ + images/message.png \ + images/edit.png \ + images/buddy.png \ + images/message32.png \ + images/presence.png \ + images/save_as.png \ + images/attach.png \ + images/mime_application.png \ + images/mime_audio.png \ + images/mime_image.png \ + images/mime_text.png \ + images/mime_video.png + +TRANSLATIONS = lang/twinkle_nl.ts \ + lang/twinkle_de.ts \ + lang/twinkle_cs.ts \ + lang/twinkle_fr.ts \ + lang/twinkle_ru.ts \ + lang/twinkle_sv.ts \ + lang/twinkle_xx.ts + +unix { + UI_DIR = .ui + MOC_DIR = .moc + OBJECTS_DIR = .obj +} + +include( ../../qtccxxincl.pro ) diff --git a/src/gui/twinkleapplication.cpp b/src/gui/twinkleapplication.cpp new file mode 100644 index 0000000..6633031 --- /dev/null +++ b/src/gui/twinkleapplication.cpp @@ -0,0 +1,47 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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().ascii()); + + // 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(); +} diff --git a/src/gui/twinkleapplication.h b/src/gui/twinkleapplication.h new file mode 100644 index 0000000..e8503f8 --- /dev/null +++ b/src/gui/twinkleapplication.h @@ -0,0 +1,44 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/gui/twinklesystray.cpp b/src/gui/twinklesystray.cpp new file mode 100644 index 0000000..d2873da --- /dev/null +++ b/src/gui/twinklesystray.cpp @@ -0,0 +1,38 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "twinklesystray.h" +#include "audits/memman.h" + +t_twinkle_sys_tray::t_twinkle_sys_tray(QWidget *pParent, const char *pszName) : +#ifdef HAVE_KDE + KSystemTray(pParent, pszName) +#else + FreeDeskSysTray(pParent, pszName) +#endif +{} + +t_twinkle_sys_tray::~t_twinkle_sys_tray() +{} + +void t_twinkle_sys_tray::dock() +{ +#ifndef HAVE_KDE + FreeDeskSysTray::dock(); +#endif +} diff --git a/src/gui/twinklesystray.h b/src/gui/twinklesystray.h new file mode 100644 index 0000000..dd03b02 --- /dev/null +++ b/src/gui/twinklesystray.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _TWINKLESYSTRAY_H +#define _TWINKLESYSTRAY_H + +#include "twinkle_config.h" + +#ifdef HAVE_KDE +#include +#include +#else +#include "freedesksystray.h" +#endif + +#include "qpopupmenu.h" +#include "qwidget.h" + +#ifdef HAVE_KDE +class t_twinkle_sys_tray : public KSystemTray { +#else +class t_twinkle_sys_tray : public FreeDeskSysTray { +#endif + +public: + t_twinkle_sys_tray(QWidget *pParent = 0, const char *pszName = 0); + ~t_twinkle_sys_tray(); + + void dock(); +}; + +#endif diff --git a/src/gui/userprofileform.ui b/src/gui/userprofileform.ui new file mode 100644 index 0000000..d9f63c2 --- /dev/null +++ b/src/gui/userprofileform.ui @@ -0,0 +1,5758 @@ + +UserProfileForm + + + UserProfileForm + + + + 0 + 0 + 783 + 594 + + + + Twinkle - User Profile + + + + unnamed + + + + layout142 + + + + unnamed + + + + profileTextLabel + + + User profile: + + + + + profileComboBox + + + + 7 + 0 + 0 + 0 + + + + Select which profile you want to edit. + + + + + + + + User + + + penguin.png + + + + + SIP server + + + package_network.png + + + + + Voice mail + + + mwi_none.png + + + + + Instant message + + + message32.png + + + + + Presence + + + presence.png + + + + + RTP audio + + + kmix.png + + + + + SIP protocol + + + package_system.png + + + + + Transport/NAT + + + yast_babelfish.png + + + + + Address format + + + yast_PhoneTTOffhook.png + + + + + Timers + + + clock.png + + + + + Ring tones + + + knotify.png + + + + + Scripts + + + edit.png + + + + + Security + + + encrypted32.png + + + + categoryListBox + + + + 1 + 7 + 0 + 0 + + + + + 150 + 0 + + + + 0 + + + Select a category for which you want to see or modify the settings. + + + + + layout12 + + + + unnamed + + + + spacer8 + + + Horizontal + + + Expanding + + + + 441 + 20 + + + + + + okPushButton + + + &OK + + + Alt+O + + + true + + + Accept and save your changes. + + + + + cancelPushButton + + + &Cancel + + + Alt+C + + + Undo all your changes and close the window. + + + + + + + settingsWidgetStack + + + + 7 + 5 + 0 + 0 + + + + Box + + + + pageUser + + + 0 + + + + unnamed + + + + userTitleTextLabel + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + User + + + 10 + + + + + accountGroupBox + + + SIP account + + + + unnamed + + + + usernameTextLabel + + + &User name*: + + + usernameLineEdit + + + + + domainTextLabel + + + &Domain*: + + + domainLineEdit + + + + + organizationTextLabel + + + Or&ganization: + + + organizationLineEdit + + + + + usernameLineEdit + + + 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. + + + + + domainLineEdit + + + 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. + + + + + organizationLineEdit + + + You may fill in the name of your organization. When you make a call, this might be shown to the called party. + + + + + displayLineEdit + + + 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. + + + + + dislpayTextLabel + + + &Your name: + + + displayLineEdit + + + + + + + authenticationGroupBox + + + SIP authentication + + + + unnamed + + + + authRealmTextLabel + + + &Realm: + + + authRealmLineEdit + + + + + authNameTextLabel + + + Authentication &name: + + + authNameLineEdit + + + + + authRealmLineEdit + + + 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. + + + + + authNameLineEdit + + + Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. + + + + + authAkaAmfTextLabel + + + AKA AM&F: + + + authAkaAmfLineEdit + + + + + authAkaOpTextLabel + + + A&KA OP: + + + authAkaOpLineEdit + + + + + authPasswordLineEdit + + + Password + + + Your password for authentication. + + + + + authPasswordTextLabel + + + &Password: + + + authPasswordLineEdit + + + + + authAkaAmfLineEdit + + + Authentication management field for AKAv1-MD5 authentication. + + + + + authAkaOpLineEdit + + + Operator variant key for AKAv1-MD5 authentication. + + + + + + + spacer9 + + + Vertical + + + Expanding + + + + 20 + 110 + + + + + + + + pageSipServer + + + 1 + + + + unnamed + + + + sipServerTitleTextLabel + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + SIP server + + + 10 + + + + + registrarGroupBox + + + Registrar + + + + unnamed + + + + registrarTextLabel + + + &Registrar: + + + registrarLineEdit + + + + + 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. + + + + + expiryTextLabel + + + &Expiry: + + + expirySpinBox + + + + + layout22 + + + + unnamed + + + + expirySpinBox + + + + 90 + 0 + + + + 999999 + + + 100 + + + The registration expiry time that Twinkle will request. + + + + + secondsTextLabel + + + seconds + + + + + spacer1 + + + Horizontal + + + Expanding + + + + 260 + 20 + + + + + + + + regAtStartupCheckBox + + + 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. + + + + + layout37 + + + + unnamed + + + + regAddQvalueCheckBox + + + 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. + + + + + regQvalueLineEdit + + + The q-value is a value between 0.000 and 1.000. A higher value means a higher priority. + + + + + spacer48 + + + Horizontal + + + Expanding + + + + 210 + 20 + + + + + + + + + + outboundProxyGroupBox + + + Outbound Proxy + + + + unnamed + + + + useProxyCheckBox + + + &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. + + + + + proxyTextLabel + + + true + + + Outbound &proxy: + + + proxyLineEdit + + + + + proxyNonResolvableCheckBox + + + &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.) + + + + + proxyLineEdit + + + true + + + The hostname, domain name or IP address of your outbound proxy. + + + + + allRequestsCheckBox + + + &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. + + + + + + + spacer10 + + + Vertical + + + Expanding + + + + 20 + 100 + + + + + + + + pageRtpAudio + + + 2 + + + + unnamed + + + + rtpAudioTitleTextLabel + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + RTP audio + + + 10 + + + + + rtpAudioTabWidget + + + + tabCodecs + + + Co&decs + + + + unnamed + + + + layout18 + + + + unnamed + + + + ptimeTextLabel + + + &G.711/G.726 payload size: + + + ptimeSpinBox + + + + + ptimeSpinBox + + + + 1 + 0 + 0 + 0 + + + + + 46 + 0 + + + + + 32767 + 32767 + + + + 50 + + + 10 + + + 10 + + + The preferred payload size for the G.711 and G.726 codecs. + + + + + payloadMsTextLabel + + + ms + + + + + spacer24 + + + Horizontal + + + Expanding + + + + 121 + 20 + + + + + + + + inFarEndCodecPrefCheckBox + + + &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. + + + + + outFarEndCodecPrefCheckBox + + + 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. + + + + + spacer38 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + codecsGroupBox + + + Codecs + + + + unnamed + + + + layout19 + + + + unnamed + + + + availCodecTextLabel + + + Available codecs: + + + + + + G.711 A-law + + + + + G.711 u-law + + + + + GSM + + + + + speex-nb (8 kHz) + + + + + speex-wb (16 kHz) + + + + + speex-uwb (32 kHz) + + + + availCodecListBox + + + Manual + + + AlwaysOff + + + List of available codecs. + + + + + + + layout14 + + + + unnamed + + + + spacer17_2 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + addCodecPushButton + + + + + + 1rightarrow.png + + + Move a codec from the list of available codecs to the list of active codecs. + + + + + rmvCodecPushButton + + + + + + 1leftarrow.png + + + Move a codec from the list of active codecs to the list of available codecs. + + + + + spacer18 + + + Vertical + + + Expanding + + + + 20 + 21 + + + + + + + + layout20 + + + + unnamed + + + + useCodecTextLabel + + + Active codecs: + + + + + activeCodecListBox + + + 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. + + + + + + + layout15 + + + + unnamed + + + + spacer19_2 + + + Vertical + + + Expanding + + + + 20 + 21 + + + + + + upCodecPushButton + + + + + + 1uparrow.png + + + Move a codec upwards in the list of active codecs, i.e. increase its preference of use. + + + + + downCodecPushButton + + + + + + 1downarrow.png + + + Move a codec downwards in the list of active codecs, i.e. decrease its preference of use. + + + + + spacer20_2 + + + Vertical + + + Expanding + + + + 20 + 31 + + + + + + + + + + + + tabPreprocessing + + + Prepr&ocessing + + + + unnamed + + + + preprocessingGroupBox + + + Preprocessing (improves quality at remote end) + + + + unnamed + + + + layout23 + + + + unnamed + + + + spxDspAgcCheckBox + + + &Automatic gain control + + + Alt+A + + + 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. + + + + + spxDspAgcLevelTextLabel + + + true + + + Automatic gain control &level: + + + spxDspAgcLevelSpinBox + + + + + spxDspAgcLevelSpinBox + + + true + + + 100 + + + 1 + + + Automatic gain control level represents percentual value of automatic gain setting of a microphone. Recommended value is about 25%. + + + + + spxDspVadCheckBox + + + &Voice activity detection + + + Alt+V + + + When enabled, voice activity detection detects whether the input signal represents a speech or a silence/background noise. + + + + + spxDspNrdCheckBox + + + &Noise reduction + + + Alt+N + + + The noise reduction can be used to reduce the amount of background noise present in the input signal. This provides higher quality speech. + + + + + spxDspAecCheckBox + + + Acoustic &Echo Cancellation + + + Alt+E + + + 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. + + + + + + + spacer24 + + + Horizontal + + + Expanding + + + + 31 + 20 + + + + + + + + spacer25 + + + Vertical + + + Expanding + + + + 20 + 121 + + + + + + + + tabIlbc + + + &iLBC + + + + unnamed + + + + ilbcGroupBox + + + iLBC + + + + unnamed + + + + layout16 + + + + unnamed + + + + ilbcPayloadTextLabel + + + i&LBC payload type: + + + ilbcPayloadSpinBox + + + + + ilbcPayloadSizeTextLabel + + + iLBC &payload size (ms): + + + ilbcPayloadSizeComboBox + + + + + + + layout17_2 + + + + unnamed + + + + ilbcPayloadSpinBox + + + 127 + + + 96 + + + The dynamic type value (96 or higher) to be used for iLBC. + + + + + + 20 + + + + + 30 + + + + ilbcPayloadSizeComboBox + + + The preferred payload size for iLBC. + + + + + + + spacer26 + + + Horizontal + + + Expanding + + + + 71 + 20 + + + + + + + + spacer29_2 + + + Vertical + + + Expanding + + + + 20 + 81 + + + + + + + + tabSpeex + + + &Speex + + + + unnamed + + + + speexGroupBox + + + Speex + + + + unnamed + + + + layout17_3 + + + + unnamed + + + + spxPenhCheckBox + + + 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). + + + + + spxUwbPayloadTextLabel + + + &Ultra wide band payload type: + + + spxUwbPayloadSpinBox + + + + + spxWbPayloadTextLabel + + + &Wide band payload type: + + + spxWbPayloadSpinBox + + + + + spxVbrCheckBox + + + Variable &bit-rate + + + 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. + + + + + spxUwbPayloadSpinBox + + + 127 + + + 96 + + + The dynamic type value (96 or higher) to be used for speex wide band. + + + + + spxDtxCheckBox + + + Discontinuous &Transmission + + + Alt+T + + + Discontinuous transmission is an addition to VAD/VBR operation, that allows to stop transmitting completely when the background noise is stationary. + + + + + spxWbPayloadSpinBox + + + 127 + + + 96 + + + The dynamic type value (96 or higher) to be used for speex wide band. + + + + + spxNbPayloadSpinBox + + + 127 + + + 96 + + + The dynamic type value (96 or higher) to be used for speex narrow band. + + + + + spxQualityTextLabel + + + &Quality: + + + spxQualitySpinBox + + + + + spxQualitySpinBox + + + 10 + + + 0 + + + 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. + + + + + spxComplexityTextLabel + + + Co&mplexity: + + + spxComplexitySpinBox + + + + + spxComplexitySpinBox + + + 10 + + + 1 + + + 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. + + + + + spxNbPayloadTextLabel + + + &Narrow band payload type: + + + spxNbPayloadSpinBox + + + + + + + spacer23_2 + + + Horizontal + + + Expanding + + + + 31 + 20 + + + + + + + + spacer30_2 + + + Vertical + + + Expanding + + + + 20 + 121 + + + + + + + + tabG726 + + + G.726 + + + + unnamed + + + + g726GroupBox + + + G.726 + + + + unnamed + + + + layout22 + + + + unnamed + + + + g72640PayloadTypeTextLabel + + + G.726 &40 kbps payload type: + + + g72640PayloadSpinBox + + + + + g72640PayloadSpinBox + + + 127 + + + 96 + + + The dynamic type value (96 or higher) to be used for G.726 40 kbps. + + + + + g72632PayloadSpinBox + + + 127 + + + 0 + + + The dynamic type value (96 or higher) to be used for G.726 32 kbps. + + + + + g72624PayloadTypeTextLabel + + + G.726 &24 kbps payload type: + + + g72624PayloadSpinBox + + + + + g72624PayloadSpinBox + + + 127 + + + 96 + + + The dynamic type value (96 or higher) to be used for G.726 24 kbps. + + + + + g72632PayloadTypeTextLabel + + + G.726 &32 kbps payload type: + + + g72632PayloadSpinBox + + + + + g72616PayloadSpinBox + + + 127 + + + 96 + + + The dynamic type value (96 or higher) to be used for G.726 16 kbps. + + + + + g72616PayloadTypeTextLabel + + + G.726 &16 kbps payload type: + + + g72616PayloadSpinBox + + + + + + + spacer31 + + + Horizontal + + + Expanding + + + + 231 + 20 + + + + + + layout41 + + + + unnamed + + + + g726PackingTextLabel + + + Codeword &packing order: + + + g726PackComboBox + + + + + + RFC 3551 + + + + + ATM AAL2 + + + + 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. + + + + + spacer40_2 + + + Horizontal + + + Expanding + + + + 141 + 20 + + + + + + + + + + spacer32 + + + Vertical + + + Expanding + + + + 20 + 150 + + + + + + + + tabDtmf + + + DT&MF + + + + unnamed + + + + dtmfGroupBox + + + DTMF + + + + unnamed + + + + spacer17 + + + Horizontal + + + Expanding + + + + 280 + 20 + + + + + + layout8 + + + + unnamed + + + + dtmfPayloadTypeSpinBox + + + + 1 + 0 + 0 + 0 + + + + + 49 + 0 + + + + + 32767 + 32767 + + + + 127 + + + 96 + + + The dynamic type value (96 or higher) to be used for DTMF events (RFC 2833). + + + + + dtmfDurationMsTextLabel + + + ms + + + + + dtmfVolumeTextLabel + + + DTMF vo&lume: + + + dtmfVolumeSpinBox + + + + + dtmfVolumeSpinBox + + + 0 + + + -63 + + + 10 + + + -10 + + + The power level of the DTMF tone in dB. + + + + + dtmfPauseSpinBox + + + + 1 + 0 + 0 + 0 + + + + + 49 + 0 + + + + + 32767 + 32767 + + + + 100 + + + 20 + + + 10 + + + The pause after a DTMF tone. + + + + + dtmfDurationTextLabel + + + DTMF &duration: + + + dtmfDurationSpinBox + + + + + dtmfPauseMsTextLabel + + + ms + + + + + dtmfPayloadTypeTextLabel + + + DTMF payload &type: + + + dtmfPayloadTypeSpinBox + + + + + dtmfPauseTextLabel + + + DTMF &pause: + + + dtmfPauseSpinBox + + + + + dtmfVolDbmTextLabel + + + dB + + + + + dtmfDurationSpinBox + + + + 1 + 0 + 0 + 0 + + + + + 49 + 0 + + + + + 32767 + 32767 + + + + 500 + + + 40 + + + 10 + + + Duration of a DTMF tone. + + + + + + + layout15 + + + + unnamed + + + + dtmfTransportTextLabel + + + DTMF t&ransport: + + + dtmfTransportComboBox + + + + + + Auto + + + + + RFC 2833 + + + + + Inband + + + + + Out-of-band (SIP INFO) + + + + 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> + + + + + spacer22_2 + + + Horizontal + + + Expanding + + + + 161 + 20 + + + + + + + + + + spacer23_3 + + + Vertical + + + Expanding + + + + 20 + 120 + + + + + + + + + + + pageSipProtocol + + + 3 + + + + unnamed + + + + sipProtocolTitleTextLabel + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + SIP protocol + + + 10 + + + + + sipProtoclTabWidget + + + + tab + + + General + + + + unnamed + + + + spacer24_2 + + + Vertical + + + Expanding + + + + 20 + 16 + + + + + + optionsGroupBox + + + Protocol options + + + + unnamed + + + + holdVariantTextLabel + + + Call &Hold variant: + + + holdVariantComboBox + + + + + + RFC 2543 + + + + + RFC 3264 + + + + holdVariantComboBox + + + + 1 + 0 + 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. + + + + + spacer4 + + + Horizontal + + + Expanding + + + + 70 + 20 + + + + + + missingContactCheckBox + + + 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. + + + + + maxForwardsCheckBox + + + &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. + + + + + regTimeCheckBox + + + 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. + + + + + compactHeadersCheckBox + + + &Use compact header names + + + Alt+U + + + Indicates if compact header names should be used for headers that have a compact form. + + + + + allowSdpChangeCheckBox + + + 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> + + + + + useDomainInContactCheckBox + + + 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> + + + + + multiValuesListCheckBox + + + &Encode Via, Route, Record-Route as list + + + Alt+E + + + 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. + + + + + + + redirectionGroupBox + + + Redirection + + + + unnamed + + + + allowRedirectionCheckBox + + + &Allow redirection + + + Alt+A + + + Indicates if Twinkle should redirect a request if a 3XX response is received. + + + + + askUserRedirectCheckBox + + + Ask user &permission to redirect + + + Alt+P + + + Indicates if Twinkle should ask the user before redirecting a request when a 3XX response is received. + + + + + maxRedirectTextLabel + + + Max re&directions: + + + maxRedirectSpinBox + + + + + maxRedirectSpinBox + + + + 1 + 0 + 0 + 0 + + + + + 46 + 0 + + + + 5 + + + 1 + + + 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. + + + + + spacer5 + + + Horizontal + + + Expanding + + + + 80 + 20 + + + + + + + + sipExtensionsGroupBox + + + SIP extensions + + + + unnamed + + + + + disabled + + + + + supported + + + + + required + + + + + preferred + + + + ext100relComboBox + + + + 1 + 0 + 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. + + + + + ext100relTextLabel + + + &100 rel (PRACK): + + + ext100relComboBox + + + + + extReplacesCheckBox + + + Replaces + + + Indicates if the Replaces-extenstion is supported. + + + + + + + + + tab + + + REFER + + + + unnamed + + + + referGroupBox + + + Call transfer (REFER) + + + + unnamed + + + + allowReferCheckBox + + + Accept call &transfer request (incoming REFER) + + + Alt+T + + + Indicates if Twinkle should transfer a call if a REFER request is received. + + + + + askUserReferCheckBox + + + 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. + + + + + refereeHoldCheckBox + + + 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. + + + + + referrerHoldCheckBox + + + 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. + + + + + refreshReferSubCheckBox + + + Auto re&fresh subscription to refer event while call transfer is not finished + + + 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. + + + + + referAorCheckBox + + + 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. + + + + + transferConsultInprogCheckBox + + + 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. + + + + + + + spacer25 + + + Vertical + + + Expanding + + + + 20 + 200 + + + + + + + + TabPage + + + Privacy + + + + unnamed + + + + privacyGroupBox + + + Privacy options + + + + unnamed + + + + pPreferredIdCheckBox + + + &Send P-Preferred-Identity header when hiding user identity + + + Alt+S + + + Include a P-Preferred-Identity header with your identity in an INVITE request for a call with identity hiding. + + + + + + + spacer40 + + + Vertical + + + Expanding + + + + 20 + 331 + + + + + + + + + + + pageNat + + + 4 + + + + unnamed + + + + NatTitleTextLabel + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + Transport/NAT + + + 10 + + + + + transportGroupBox + + + SIP transport + + + + unnamed + + + + + Auto + + + + + UDP + + + + + TCP + + + + sipTransportComboBox + + + 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. + + + + + spacer46 + + + Horizontal + + + Expanding + + + + 151 + 20 + + + + + + sipTransportTextLabel + + + T&ransport protocol: + + + sipTransportComboBox + + + + + udpThresholdTextLabel + + + UDP t&hreshold: + + + udpThresholdSpinBox + + + + + udpThresholdSpinBox + + + bytes + + + 65535 + + + 100 + + + 1300 + + + Messages larger than the threshold are sent via TCP. Smaller messages are sent via UDP. + + + + + spacer47 + + + Horizontal + + + Expanding + + + + 81 + 20 + + + + + + + + natTraversalButtonGroup_ + + + NAT traversal + + + + unnamed + + + + natNoneRadioButton + + + &NAT traversal not needed + + + 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. + + + + + natStaticRadioButton + + + &Use statically configured public IP address inside SIP messages + + + Alt+U + + + 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. + + + + + layout32 + + + + unnamed + + + + publicIPTextLabel + + + &Public IP address: + + + 21 + + + publicIPLineEdit + + + + + publicIPLineEdit + + + The public IP address of your NAT. + + + + + + + natStunRadioButton + + + Use &STUN (does not work for incoming TCP) + + + Alt+S + + + Choose this option when your SIP provider offers a STUN server for NAT traversal. + + + + + layout33 + + + + unnamed + + + + stunServerTextLabel + + + S&TUN server: + + + 21 + + + stunServerLineEdit + + + + + stunServerLineEdit + + + The hostname, domain name or IP address of the STUN server. + + + + + + + persistentTcpCheckBox + + + P&ersistent TCP connection + + + Alt+E + + + 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. + + + + + natKeepaliveCheckBox + + + Enable NAT &keep alive + + + Alt+K + + + Send UDP NAT keep alive packets. + + + + + + + spacer19 + + + Vertical + + + Expanding + + + + 20 + 80 + + + + + + + + pageAddressFormat + + + 5 + + + + unnamed + + + + addressFormatTitleTextLabel + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + Address format + + + 10 + + + + + telNumberGroupBox + + + Telephone numbers + + + + unnamed + + + + displayTelUserCheckBox + + + Only &display user part of URI for telephone number + + + Alt+D + + + 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. + + + + + numericalUserIsTelCheckBox + + + &URI with numerical user part is a telephone number + + + Alt+U + + + 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. + + + + + removeSpecialCheckBox + + + &Remove special symbols from numerical dial strings + + + Alt+R + + + 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. + + + + + useTelUriCheckBox + + + Use tel-URI for telephone &number + + + Alt+N + + + Expand a dialed telephone number to a tel-URI instead of a sip-URI. + + + + + specialTextLabel + + + &Special symbols: + + + specialLineEdit + + + + + specialLineEdit + + + The special symbols that may be part of a telephone number for nice formatting, but must be removed when dialing. + + + + + + + conversionGroupBox + + + Number conversion + + + + unnamed + + + + layout29 + + + + unnamed + + + + + Match expression + + + true + + + true + + + + + Replace + + + true + + + true + + + + conversionListView + + + true + + + AllColumns + + + <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> + + + + + layout15_2 + + + + unnamed + + + + spacer19_2_2 + + + Vertical + + + Expanding + + + + 20 + 21 + + + + + + upConversionPushButton + + + + + + 1uparrow.png + + + Move the selected number conversion rule upwards in the list. + + + + + downConversionPushButton + + + + + + 1downarrow.png + + + Move the selected number conversion rule downwards in the list. + + + + + spacer20_2_2 + + + Vertical + + + Expanding + + + + 20 + 31 + + + + + + + + + + layout30 + + + + unnamed + + + + addConversionPushButton + + + &Add + + + Alt+A + + + Add a number conversion rule. + + + + + removePushButton + + + Re&move + + + Alt+M + + + Remove the selected number conversion rule. + + + + + editConversionPushButton + + + &Edit + + + Alt+E + + + Edit the selected number conversion rule. + + + + + spacer38_2 + + + Horizontal + + + Expanding + + + + 291 + 20 + + + + + + + + layout24 + + + + unnamed + + + + testConversionLineEdit + + + Type a telephone number here an press the Test button to see how it is converted by the list of number conversion rules. + + + + + testConversionPushButton + + + &Test + + + Alt+T + + + Test how a number is converted by the number conversion rules. + + + + + + + + + spacer20 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + + + pageTimers + + + 6 + + + + unnamed + + + + timersTitleTextLabel + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + Timers + + + 10 + + + + + layout7 + + + + unnamed + + + + layout6 + + + + unnamed + + + + secNoanswerTextLabel + + + seconds + + + + + tmrNatKeepaliveSpinBox + + + + 0 + 0 + 0 + 0 + + + + + 55 + 0 + + + + + 55 + 32767 + + + + 900 + + + 10 + + + 10 + + + 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. + + + + + tmrNoanswerSpinBox + + + + 0 + 0 + 0 + 0 + + + + + 55 + 0 + + + + + 55 + 32767 + + + + 600 + + + 10 + + + 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". + + + + + tmrNatKeepaliveTextLabel + + + NAT &keep alive: + + + tmrNatKeepaliveSpinBox + + + + + tmrNoanswerTextLabel + + + &No answer: + + + tmrNoanswerSpinBox + + + + + + + spacer23 + + + Horizontal + + + Expanding + + + + 270 + 20 + + + + + + + + spacer22 + + + Vertical + + + Expanding + + + + 20 + 450 + + + + + + + + pageRingTones + + + 7 + + + + unnamed + + + + ringtonesTitleTextLabel + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + Ring tones + + + 10 + + + + + layout18 + + + + unnamed + + + + openRingbackToolButton + + + TabFocus + + + + + + fileopen.png + + + Select ring back tone file. + + + + + openRingtoneToolButton + + + TabFocus + + + + + + fileopen.png + + + Select ring tone file. + + + + + ringbackTextLabel + + + Ring &back tone: + + + ringbackLineEdit + + + + + 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> + + + + + ringtoneLineEdit + + + <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> + + + + + ringtoneTextLabel + + + &Ring tone: + + + ringtoneLineEdit + + + + + + + spacer30 + + + Vertical + + + Expanding + + + + 20 + 391 + + + + + + + + pageScripts + + + 8 + + + + unnamed + + + + scriptsTitleTextLabel + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + Scripts + + + 10 + + + + + layout19 + + + + unnamed + + + + localReleaseLineEdit + + + <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>. + + + + + openInCallFailedToolButton + + + TabFocus + + + + + + fileopen.png + + + Select script file. + + + + + openIncomingCallScriptToolButton + + + TabFocus + + + + + + fileopen.png + + + Select script file. + + + + + openOutCallAnsweredToolButton + + + TabFocus + + + + + + fileopen.png + + + Select script file. + + + + + inCallFailedLineEdit + + + <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>. + + + + + openInCallAnsweredToolButton + + + TabFocus + + + + + + fileopen.png + + + Select script file. + + + + + remoteReleaseLineEdit + + + <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>. + + + + + openOutCallToolButton + + + TabFocus + + + + + + fileopen.png + + + Select script file. + + + + + incomingCallScriptLineEdit + + + <p> +You can customize the way Twinkle handles incoming calls. Twinkle can call a script when a call comes in. Based on the ouput 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 outputing 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>. + + + + + outCallAnsweredLineEdit + + + <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>. + + + + + inCallAnsweredLineEdit + + + <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>. + + + + + localReleaseTextLabel + + + Call released locall&y: + + + inCallFailedLineEdit + + + + + openOutCallFailedToolButton + + + TabFocus + + + + + + fileopen.png + + + Select script file. + + + + + outCallFailedLineEdit + + + <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>. + + + + + outCallLineEdit + + + <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>. + + + + + outCallAnsweredTextLabel + + + Outgoing call a&nswered: + + + inCallAnsweredLineEdit + + + + + inCallFailedTextLabel + + + Incoming call &failed: + + + inCallFailedLineEdit + + + + + incomingCallScriptTextLabel + + + &Incoming call: + + + incomingCallScriptLineEdit + + + + + openLocalReleaseToolButton + + + TabFocus + + + + + + fileopen.png + + + Select script file. + + + + + remoteReleaseTextLabel + + + Call released &remotely: + + + inCallFailedLineEdit + + + + + inCallAnsweredTextLabel + + + Incoming call &answered: + + + inCallAnsweredLineEdit + + + + + openRemoteReleaseToolButton + + + TabFocus + + + + + + fileopen.png + + + Select script file. + + + + + outCallTextLabel + + + O&utgoing call: + + + incomingCallScriptLineEdit + + + + + outCallFailedTextLabel + + + Out&going call failed: + + + inCallFailedLineEdit + + + + + + + spacer29 + + + Vertical + + + Expanding + + + + 20 + 190 + + + + + + + + pageSecurity + + + 9 + + + + unnamed + + + + securityTitleTextLabel + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + Security + + + 10 + + + + + zrtpEnabledCheckBox + + + &Enable ZRTP/SRTP encryption + + + Alt+E + + + 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. + + + + + zrtpSettingsGroupBox + + + ZRTP settings + + + + unnamed + + + + zrtpSendIfSupportedCheckBox + + + O&nly encrypt audio if remote party indicated ZRTP support in SDP + + + Alt+N + + + 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. + + + + + zrtpSdpCheckBox + + + &Indicate ZRTP support in SDP + + + Alt+I + + + Twinkle will indicate ZRTP support during call setup in its signalling. + + + + + zrtpGoClearWarningCheckBox + + + &Popup warning when remote party disables encryption during call + + + Alt+P + + + 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. + + + + + + + spacer33 + + + Vertical + + + Expanding + + + + 20 + 241 + + + + + + + + pageVoiceMail + + + 10 + + + + unnamed + + + + voiceMailTextLabel + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + Voice mail + + + 10 + + + + + layout39 + + + + unnamed + + + + vmAddressTextLabel + + + &Voice mail address: + + + vmAddressLineEdit + + + + + vmAddressLineEdit + + + The SIP address or telephone number to access your voice mail. + + + + + layout38 + + + + unnamed + + + + + Unsollicited + + + + + Sollicited + + + + mwiTypeComboBox + + + <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> + + + + + spacer39 + + + Horizontal + + + Expanding + + + + 221 + 20 + + + + + + + + mwiTypeTextLabel + + + &MWI type: + + + mwiTypeComboBox + + + + + + + mwiSollicitedGroupBox + + + Sollicited MWI + + + + unnamed + + + + layout36 + + + + unnamed + + + + spacer35 + + + Horizontal + + + Expanding + + + + 120 + 20 + + + + + + mwiDurationTextLabel + + + Subscription &duration: + + + mwiDurationSpinBox + + + + + mwiUserTextLabel + + + Mailbox &user name: + + + mwiUserLineEdit + + + + + mwiServerLineEdit + + + The hostname, domain name or IP address of your voice mailbox server. + + + + + layout35 + + + + unnamed + + + + mwiDurationSpinBox + + + + 90 + 0 + + + + 999999 + + + 100 + + + 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. + + + + + mwiSecondsTextLabel + + + seconds + + + + + spacer36 + + + Horizontal + + + Expanding + + + + 190 + 20 + + + + + + + + mwiUserLineEdit + + + Your user name for accessing your voice mailbox. + + + + + mwiServerTextLabel + + + Mailbox &server: + + + mwiServerLineEdit + + + + + mwiViaProxyCheckBox + + + Via outbound &proxy + + + Alt+P + + + Check this option if Twinkle should send SIP messages to the mailbox server via the outbound proxy. + + + + + + + + + spacer38_3 + + + Vertical + + + Expanding + + + + 20 + 211 + + + + + + + + pageIM + + + 11 + + + + unnamed + + + + imTextLabel + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + Instant message + + + 10 + + + + + layout74 + + + + unnamed + + + + imMaxSessionsTextLabel + + + &Maximum number of sessions: + + + imMaxSessionsSpinBox + + + + + imMaxSessionsSpinBox + + + 65535 + + + When you have this number of instant message sessions open, new incoming message sessions will be rejected. + + + + + spacer42 + + + Horizontal + + + Expanding + + + + 201 + 20 + + + + + + + + isComposingCheckBox + + + &Send composing indications when typing a message. + + + Alt+S + + + Twinkle sends a composing indication when you type a message. This way the recipient can see that you are typing. + + + + + spacer40_3 + + + Vertical + + + Expanding + + + + 20 + 350 + + + + + + + + pagePresence + + + 12 + + + + unnamed + + + + presTextLabel + + + + 150 + 150 + 150 + + + + + 21 + + + + Box + + + Presence + + + 10 + + + + + presYourGroupBox + + + Your presence + + + + unnamed + + + + presPublishCheckBox + + + &Publish availability at startup + + + Alt+P + + + Publish your availability at startup. + + + + + layout75 + + + + unnamed + + + + presPublishTimerTextLabel + + + Publication &refresh interval (sec): + + + presPublishTimeSpinBox + + + + + presPublishTimeSpinBox + + + 999999 + + + 100 + + + Refresh rate of presence publications. + + + + + spacer43 + + + Horizontal + + + Expanding + + + + 231 + 20 + + + + + + + + + + groupBox25 + + + Buddy presence + + + + unnamed + + + + layout76 + + + + unnamed + + + + presSubscribeTimerTextLabel + + + &Subscription refresh interval (sec): + + + presSubscribeTimeSpinBox + + + + + presSubscribeTimeSpinBox + + + 999999 + + + 100 + + + Refresh rate of presence subscriptions. + + + + + spacer44 + + + Horizontal + + + Expanding + + + + 191 + 20 + + + + + + + + + + spacer45 + + + Vertical + + + Expanding + + + + 20 + 281 + + + + + + + + + + + categoryListBox + highlighted(int) + UserProfileForm + showCategory(int) + + + cancelPushButton + clicked() + UserProfileForm + reject() + + + okPushButton + clicked() + UserProfileForm + validate() + + + useProxyCheckBox + toggled(bool) + proxyTextLabel + setEnabled(bool) + + + useProxyCheckBox + toggled(bool) + proxyLineEdit + setEnabled(bool) + + + useProxyCheckBox + toggled(bool) + allRequestsCheckBox + setEnabled(bool) + + + allowRedirectionCheckBox + toggled(bool) + askUserRedirectCheckBox + setEnabled(bool) + + + allowRedirectionCheckBox + toggled(bool) + maxRedirectTextLabel + setEnabled(bool) + + + allowRedirectionCheckBox + toggled(bool) + maxRedirectSpinBox + setEnabled(bool) + + + useProxyCheckBox + toggled(bool) + proxyNonResolvableCheckBox + setEnabled(bool) + + + natStaticRadioButton + toggled(bool) + publicIPTextLabel + setEnabled(bool) + + + natStaticRadioButton + toggled(bool) + publicIPLineEdit + setEnabled(bool) + + + natStunRadioButton + toggled(bool) + stunServerTextLabel + setEnabled(bool) + + + natStunRadioButton + toggled(bool) + stunServerLineEdit + setEnabled(bool) + + + allowReferCheckBox + toggled(bool) + askUserReferCheckBox + setEnabled(bool) + + + allowReferCheckBox + toggled(bool) + refereeHoldCheckBox + setEnabled(bool) + + + profileComboBox + activated(const QString&) + UserProfileForm + changeProfile(const QString&) + + + openRingtoneToolButton + clicked() + UserProfileForm + chooseRingtone() + + + openRingbackToolButton + clicked() + UserProfileForm + chooseRingback() + + + openIncomingCallScriptToolButton + clicked() + UserProfileForm + chooseIncomingCallScript() + + + addCodecPushButton + clicked() + UserProfileForm + addCodec() + + + rmvCodecPushButton + clicked() + UserProfileForm + removeCodec() + + + upCodecPushButton + clicked() + UserProfileForm + upCodec() + + + downCodecPushButton + clicked() + UserProfileForm + downCodec() + + + availCodecListBox + doubleClicked(QListBoxItem*) + UserProfileForm + addCodec() + + + activeCodecListBox + doubleClicked(QListBoxItem*) + UserProfileForm + removeCodec() + + + openInCallAnsweredToolButton + clicked() + UserProfileForm + chooseInCallAnsweredScript() + + + openInCallFailedToolButton + clicked() + UserProfileForm + chooseInCallFailedScript() + + + openLocalReleaseToolButton + clicked() + UserProfileForm + chooseLocalReleaseScript() + + + openOutCallAnsweredToolButton + clicked() + UserProfileForm + chooseOutCallAnsweredScript() + + + openOutCallFailedToolButton + clicked() + UserProfileForm + chooseOutCallFailedScript() + + + openOutCallToolButton + clicked() + UserProfileForm + chooseOutgoingCallScript() + + + openRemoteReleaseToolButton + clicked() + UserProfileForm + chooseRemoteReleaseScript() + + + upConversionPushButton + clicked() + UserProfileForm + upConversion() + + + downConversionPushButton + clicked() + UserProfileForm + downConversion() + + + addConversionPushButton + clicked() + UserProfileForm + addConversion() + + + editConversionPushButton + clicked() + UserProfileForm + editConversion() + + + removePushButton + clicked() + UserProfileForm + removeConversion() + + + testConversionPushButton + clicked() + UserProfileForm + testConversion() + + + zrtpEnabledCheckBox + toggled(bool) + zrtpSettingsGroupBox + setEnabled(bool) + + + mwiTypeComboBox + activated(int) + UserProfileForm + changeMWIType(int) + + + regAddQvalueCheckBox + toggled(bool) + regQvalueLineEdit + setEnabled(bool) + + + sipTransportComboBox + activated(int) + UserProfileForm + changeSipTransportProtocol(int) + + + spxDspAgcCheckBox + toggled(bool) + spxDspAgcLevelTextLabel + setEnabled(bool) + + + spxDspAgcCheckBox + toggled(bool) + spxDspAgcLevelSpinBox + setEnabled(bool) + + + natStunRadioButton + toggled(bool) + natKeepaliveCheckBox + setDisabled(bool) + + + + 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 + natNoneRadioButton + natStaticRadioButton + publicIPLineEdit + natStunRadioButton + stunServerLineEdit + persistentTcpCheckBox + 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 + + + qlistbox.h + qlineedit.h + qlabel.h + qcombobox.h + qspinbox.h + qregexp.h + sdp/sdp.h + qvalidator.h + protocol.h + qmessagebox.h + gui.h + qfiledialog.h + qfileinfo.h + qstringlist.h + twinkle_config.h + qlistview.h + numberconversionform.h + util.h + user.h + qvaluelist.h + map + list + userprofileform.ui.h + + + map<t_user *, int> map_last_cat; + t_user *current_profile; + int current_profile_idx; + list<t_user *> profile_list; + + + stunServerChanged(t_user *) + authCredentialsChanged(t_user *, const string &) + sipUserChanged(t_user *) + success() + mwiChangeUnsubscribe(t_user *) + mwiChangeSubscribe(t_user *) + + + showCategory( int index ) + populate() + initProfileList( list<t_user *> profiles, QString show_profile_name ) + show( list<t_user *> profiles, QString show_profile ) + validate() + changeProfile( const QString & profileName ) + chooseFile( QLineEdit * qle, const QString & filter, const QString & caption ) + chooseRingtone() + chooseRingback() + chooseIncomingCallScript() + chooseInCallAnsweredScript() + chooseInCallFailedScript() + chooseOutgoingCallScript() + chooseOutCallAnsweredScript() + chooseOutCallFailedScript() + chooseLocalReleaseScript() + chooseRemoteReleaseScript() + addCodec() + removeCodec() + upCodec() + downCodec() + upConversion() + downConversion() + addConversion() + editConversion() + removeConversion() + testConversion() + changeMWIType( int idxMWIType ) + changeSipTransportProtocol( int idx ) + + + init() + label2codec( const QString & label ) + codec2label( t_audio_codec & codec ) + ext_support2indexComboItem( t_ext_support ext ) + indexComboItem2ext_support( int index ) + exec( list<t_user *> profiles, QString show_profile ) + check_dynamic_payload( QSpinBox * spb, QValueList<int> & checked_list ) + get_number_conversions() + validateValues() + + + + diff --git a/src/gui/userprofileform.ui.h b/src/gui/userprofileform.ui.h new file mode 100644 index 0000000..ad9b5f5 --- /dev/null +++ b/src/gui/userprofileform.ui.h @@ -0,0 +1,1458 @@ +/**************************************************************************** +** 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, preserving your code. Create 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// 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" + +// 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 + +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(rtpAudioTabWidget->page(idxRtpSpeex), false); + rtpAudioTabWidget->setTabEnabled(rtpAudioTabWidget->page(idxRtpPreprocessing), false); +#endif +#ifndef HAVE_ILBC + // iLBC + ilbcGroupBox->hide(); + rtpAudioTabWidget->setTabEnabled(rtpAudioTabWidget->page(idxRtpIlbc), false); +#endif +#ifndef HAVE_ZRTP + // Zrtp + zrtpEnabledCheckBox->setEnabled(false); + zrtpSettingsGroupBox->hide(); +#endif + + // Set toolbutton icons for disabled options. + QIconSet i; + i = openRingtoneToolButton->iconSet(); + i.setPixmap(QPixmap::fromMimeSource("fileopen-disabled.png"), + QIconSet::Automatic, QIconSet::Disabled); + openRingtoneToolButton->setIconSet(i); + openRingbackToolButton->setIconSet(i); + openIncomingCallScriptToolButton->setIconSet(i); +} + +void UserProfileForm::showCategory( int index ) +{ + if (index == idxCatUser) { + settingsWidgetStack->raiseWidget(pageUser); + } else if (index == idxCatSipServer) { + settingsWidgetStack->raiseWidget(pageSipServer); + } else if (index == idxCatVoiceMail) { + settingsWidgetStack->raiseWidget(pageVoiceMail); + } else if (index == idxCatIM) { + settingsWidgetStack->raiseWidget(pageIM); + } else if (index == idxCatPresence) { + settingsWidgetStack->raiseWidget(pagePresence); + } else if (index == idxCatRtpAudio) { + settingsWidgetStack->raiseWidget(pageRtpAudio); + } else if (index == idxCatSipProtocol) { + settingsWidgetStack->raiseWidget(pageSipProtocol); + } else if (index == idxCatNat) { + settingsWidgetStack->raiseWidget(pageNat); + } else if (index == idxCatAddrFmt) { + settingsWidgetStack->raiseWidget(pageAddressFormat); + } else if (index == idxCatTimers) { + settingsWidgetStack->raiseWidget(pageTimers); + } else if (index == idxCatRingTones) { + settingsWidgetStack->raiseWidget(pageRingTones); + } else if (index == idxCatScripts) { + settingsWidgetStack->raiseWidget(pageScripts); + } else if (index == idxCatSecurity) { + settingsWidgetStack->raiseWidget(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; + } + 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; + 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()); + setCaption(s); + + // Select the User category + categoryListBox->setSelected(idxCatUser, true); + settingsWidgetStack->raiseWidget(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->setCurrentItem(idxMWISollicited); + mwiSollicitedGroupBox->setEnabled(true); + } else { + mwiTypeComboBox->setCurrentItem(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); + activeCodecListBox->clear(); + list audio_codecs = current_profile->get_codecs(); + for (list::iterator i = audio_codecs.begin(); i != audio_codecs.end(); i++) + { + activeCodecListBox->insertItem(codec2label(*i)); + allCodecs.remove(codec2label(*i)); + } + availCodecListBox->clear(); + if (!allCodecs.empty()) availCodecListBox->insertStringList(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->setCurrentItem(idxIlbcMode20); + } else { + ilbcPayloadSizeComboBox->setCurrentItem(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->setCurrentItem(idxG726PackRfc3551); + } else { + g726PackComboBox->setCurrentItem(idxG726PackAal2); + } + + // DTMF + switch (current_profile->get_dtmf_transport()) { + case DTMF_RFC2833: + dtmfTransportComboBox->setCurrentItem(idxDtmfRfc2833); + break; + case DTMF_INBAND: + dtmfTransportComboBox->setCurrentItem(idxDtmfInband); + break; + case DTMF_INFO: + dtmfTransportComboBox->setCurrentItem(idxDtmfInfo); + break; + default: + dtmfTransportComboBox->setCurrentItem(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->setCurrentItem(idxHoldRfc2543); + break; + default: + holdVariantComboBox->setCurrentItem(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->setCurrentItem( + 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->setCurrentItem(idxSipTransportUDP); + break; + case SIP_TRANS_TCP: + sipTransportComboBox->setCurrentItem(idxSipTransportTCP); + break; + default: + sipTransportComboBox->setCurrentItem(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()); + + conversionListView->clear(); + conversionListView->setSorting(-1); + list conversions = current_profile->get_number_conversions(); + for (list::reverse_iterator i = conversions.rbegin(); i != conversions.rend(); i++) + { + new QListViewItem(conversionListView, i->re.str().c_str(), i->fmt.c_str()); + } + + // 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->insertItem((*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->setCurrentItem(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, + QValueList &checked_list) +{ + if (checked_list.contains(spb->value())) { + categoryListBox->setSelected(idxCatRtpAudio, true); + settingsWidgetStack->raiseWidget(pageRtpAudio); + QString msg = tr("Dynamic payload type %1 is used more than once.").arg(spb->value()); + ((t_gui *)ui)->cb_show_msg(this, msg.ascii(), MSG_CRITICAL); + spb->setFocus(); + return false; + } + + checked_list.append(spb->value()); + return true; +} + +list UserProfileForm::get_number_conversions() +{ + list conversions; + QListViewItemIterator it(conversionListView); + while (it.current()) { + QListViewItem *item = it.current(); + t_number_conversion c; + + try { + c.re.assign(item->text(colExpr).ascii()); + c.fmt = item->text(colReplace).ascii(); + conversions.push_back(c); + } catch (boost::bad_expression) { + // Should never happen as validity has been + // checked already. Just being defensive here. + } + + ++it; + } + + return conversions; +} + +bool UserProfileForm::validateValues() +{ + QString s; + + // Validity check user page + // SIP username is mandatory + if (usernameLineEdit->text().isEmpty()) { + categoryListBox->setSelected(idxCatUser, true); + settingsWidgetStack->raiseWidget(pageUser); + ((t_gui *)ui)->cb_show_msg(this, tr("You must fill in a user name for your SIP account.").ascii(), + MSG_CRITICAL); + usernameLineEdit->setFocus(); + return false; + } + + // SIP user domain is mandatory + if (domainLineEdit->text().isEmpty()) { + categoryListBox->setSelected(idxCatUser, true); + settingsWidgetStack->raiseWidget(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.").ascii(), + MSG_CRITICAL); + domainLineEdit->setFocus(); + return false; + } + + // Check validity of domain + s = USER_SCHEME; + s.append(':').append(domainLineEdit->text()); + t_url u_domain(s.ascii()); + if (!u_domain.is_valid() || u_domain.get_user() != "") { + categoryListBox->setSelected(idxCatUser, true); + settingsWidgetStack->raiseWidget(pageUser); + ((t_gui *)ui)->cb_show_msg(this, tr("Invalid domain.").ascii(), 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.ascii()); + if (!u_user_domain.is_valid()) { + categoryListBox->setSelected(idxCatUser, true); + settingsWidgetStack->raiseWidget(pageUser); + ((t_gui *)ui)->cb_show_msg(this, tr("Invalid user name.").ascii(), MSG_CRITICAL); + usernameLineEdit->setFocus(); + return false; + } + + // Registrar + if (!registrarLineEdit->text().isEmpty()) { + s = USER_SCHEME; + s.append(':').append(registrarLineEdit->text()); + t_url u(s.ascii()); + if (!u.is_valid() || u.get_user() != "") { + categoryListBox->setSelected(idxCatSipServer, true); + settingsWidgetStack->raiseWidget(pageSipServer); + ((t_gui *)ui)->cb_show_msg(this, tr("Invalid value for registrar.").ascii(), + 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.ascii()); + if (!u.is_valid() || u.get_user() != "") { + categoryListBox->setSelected(idxCatSipServer, true); + settingsWidgetStack->raiseWidget(pageSipServer); + ((t_gui *)ui)->cb_show_msg(this, tr("Invalid value for outbound proxy.").ascii(), + MSG_CRITICAL); + proxyLineEdit->setFocus(); + proxyLineEdit->selectAll(); + return false; + } + } + + + // Validity check voice mail page + if (mwiTypeComboBox->currentItem() == idxMWISollicited) { + // Mailbox user name is mandatory + if (mwiUserLineEdit->text().isEmpty()) { + categoryListBox->setSelected(idxCatVoiceMail, true); + settingsWidgetStack->raiseWidget(pageVoiceMail); + ((t_gui *)ui)->cb_show_msg(this, + tr("You must fill in a mailbox user name.").ascii(), + MSG_CRITICAL); + mwiUserLineEdit->setFocus(); + return false; + } + + // Mailbox server is mandatory + if (mwiServerLineEdit->text().isEmpty()) { + categoryListBox->setSelected(idxCatVoiceMail, true); + settingsWidgetStack->raiseWidget(pageVoiceMail); + ((t_gui *)ui)->cb_show_msg(this, + tr("You must fill in a mailbox server").ascii(), + MSG_CRITICAL); + mwiServerLineEdit->setFocus(); + return false; + } + + // Check validity of mailbox server + s = USER_SCHEME; + s.append(':').append(mwiServerLineEdit->text()); + t_url u_server(s.ascii()); + if (!u_server.is_valid() || u_server.get_user() != "") { + categoryListBox->setSelected(idxCatVoiceMail, true); + settingsWidgetStack->raiseWidget(pageVoiceMail); + ((t_gui *)ui)->cb_show_msg(this, tr("Invalid mailbox server.").ascii(), + 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.ascii()); + if (!u_user_server.is_valid()) { + categoryListBox->setSelected(idxCatVoiceMail, true); + settingsWidgetStack->raiseWidget(pageVoiceMail); + ((t_gui *)ui)->cb_show_msg(this, tr("Invalid mailbox user name.").ascii(), + MSG_CRITICAL); + mwiUserLineEdit->setFocus(); + return false; + } + } + + // NAT public IP + if (natStaticRadioButton->isChecked()) { + if (publicIPLineEdit->text().isEmpty()){ + categoryListBox->setSelected(idxCatNat, true); + settingsWidgetStack->raiseWidget(pageNat); + ((t_gui *)ui)->cb_show_msg(this, tr("Value for public IP address missing.").ascii(), + MSG_CRITICAL); + publicIPLineEdit->setFocus(); + return false; + } + } + + // Check for double RTP dynamic payload types + QValueList checked_types; + if (!check_dynamic_payload(spxNbPayloadSpinBox, checked_types) || + !check_dynamic_payload(spxWbPayloadSpinBox, checked_types) || + !check_dynamic_payload(spxUwbPayloadSpinBox, checked_types)) + { + rtpAudioTabWidget->showPage(tabSpeex); + return false; + } + + if (!check_dynamic_payload(ilbcPayloadSpinBox, checked_types)) { + rtpAudioTabWidget->showPage(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->showPage(tabG726); + return false; + } + + if (!check_dynamic_payload(dtmfPayloadTypeSpinBox, checked_types)) { + rtpAudioTabWidget->showPage(tabDtmf); + return false; + } + + // STUN server + if (natStunRadioButton->isChecked()) { + s = "stun:"; + s.append(stunServerLineEdit->text()); + t_url u(s.ascii()); + if (!u.is_valid() || u.get_user() != "") { + categoryListBox->setSelected(idxCatNat, true); + settingsWidgetStack->raiseWidget(pageNat); + ((t_gui *)ui)->cb_show_msg(this, tr("Invalid value for STUN server.").ascii(), + 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->currentItem() == 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().ascii() || + current_profile->get_display(false) != displayLineEdit->text().ascii() || + current_profile->get_domain() != domainLineEdit->text().ascii()) + { + current_profile->set_display(displayLineEdit->text().ascii()); + current_profile->set_name(usernameLineEdit->text().ascii()); + current_profile->set_domain (domainLineEdit->text().ascii()); + emit sipUserChanged(current_profile); + } + + current_profile->set_organization(organizationLineEdit->text().ascii()); + + 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().ascii(), '0', 32), new_aka_op); + hex2binary(padleft(authAkaAmfLineEdit->text().ascii(), '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().ascii() || + current_profile->get_auth_name() != authNameLineEdit->text().ascii() || + current_profile->get_auth_pass() != authPasswordLineEdit->text().ascii() || + 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().ascii()); + current_profile->set_auth_name(authNameLineEdit->text().ascii()); + current_profile->set_auth_pass(authPasswordLineEdit->text().ascii()); + 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.ascii())); + 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(atof(regQvalueLineEdit->text().ascii())); + + current_profile->set_use_outbound_proxy(useProxyCheckBox->isChecked()); + s = USER_SCHEME; + s.append(':').append(proxyLineEdit->text()); + current_profile->set_outbound_proxy(t_url(s.ascii())); + 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().ascii()); + + bool mustTriggerMWISubscribe = false; + bool mwiSollicited = (mwiTypeComboBox->currentItem() == 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().ascii() != current_profile->get_mwi_user() || + t_url(s.ascii()) != 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().ascii()); + s = USER_SCHEME; + s.append(':').append(mwiServerLineEdit->text()); + current_profile->set_mwi_server(t_url(s.ascii())); + 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 (size_t i = 0; i < activeCodecListBox->count(); i++) { + audio_codecs.push_back(label2codec(activeCodecListBox->text(i))); + } + 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->currentItem()) { + 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->currentItem()) { + case idxG726PackRfc3551: + current_profile->set_g726_packing(G726_PACK_RFC3551); + break; + default: + current_profile->set_g726_packing(G726_PACK_AAL2); + break; + } + + // DTMF + switch (dtmfTransportComboBox->currentItem()) { + 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->currentItem()) { + 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->currentItem())); + 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->currentItem()) { + 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().ascii()); + current_profile->set_use_stun(natStunRadioButton->isChecked()); + + if (current_profile->get_stun_server().encode_noscheme() != stunServerLineEdit->text().ascii() || + current_profile->get_enable_nat_keepalive() != natKeepaliveCheckBox->isChecked()) + { + s = "stun:"; + s.append(stunServerLineEdit->text()); + current_profile->set_stun_server(t_url(s.ascii())); + 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().stripWhiteSpace().ascii()); + 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().stripWhiteSpace().ascii()); + current_profile->set_ringback_file(ringbackLineEdit->text().stripWhiteSpace().ascii()); + + // SCRIPTS + current_profile->set_script_incoming_call(incomingCallScriptLineEdit-> + text().stripWhiteSpace().ascii()); + current_profile->set_script_in_call_answered(inCallAnsweredLineEdit-> + text().stripWhiteSpace().ascii()); + current_profile->set_script_in_call_failed(inCallFailedLineEdit-> + text().stripWhiteSpace().ascii()); + current_profile->set_script_outgoing_call(outCallLineEdit-> + text().stripWhiteSpace().ascii()); + current_profile->set_script_out_call_answered(outCallAnsweredLineEdit-> + text().stripWhiteSpace().ascii()); + current_profile->set_script_out_call_failed(outCallFailedLineEdit-> + text().stripWhiteSpace().ascii()); + current_profile->set_script_local_release(localReleaseLineEdit-> + text().stripWhiteSpace().ascii()); + current_profile->set_script_remote_release(remoteReleaseLineEdit-> + text().stripWhiteSpace().ascii()); + + // 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->setCurrentItem(current_profile_idx); + return; + } + + // Store the current viewed category + map_last_cat[current_profile] = categoryListBox->index(categoryListBox->selectedItem()); + + // Change to new profile. + for (list::iterator i = profile_list.begin(); i != profile_list.end(); i++) { + if ((*i)->get_profile_name() == profileName.ascii()) { + current_profile = *i; + break; + } + } + + current_profile_idx = profileComboBox->currentItem(); + populate(); + + // Restore last viewed category + int idxCat = map_last_cat[current_profile]; + categoryListBox->setSelected(idxCat, true); + showCategory(idxCat); +} + +void UserProfileForm::chooseFile(QLineEdit *qle, const QString &filter, const QString &caption) +{ + QString file = QFileDialog::getOpenFileName( + ((t_gui *)ui)->get_last_file_browse_path(), + filter, this, "open file dialog", + caption); + if (!file.isEmpty()) { + qle->setText(file); + ((t_gui *)ui)->set_last_file_browse_path(QFileInfo(file).dirPath(true)); + } +} + +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 (size_t i = 0; i < availCodecListBox->count(); i++) { + if (availCodecListBox->isSelected(i)) { + activeCodecListBox->insertItem(availCodecListBox->text(i)); + activeCodecListBox->setSelected( + activeCodecListBox->count() - 1, true); + availCodecListBox->removeItem(i); + return; + } + } +} + +void UserProfileForm::removeCodec() { + for (size_t i = 0; i < activeCodecListBox->count(); i++) { + if (activeCodecListBox->isSelected(i)) { + availCodecListBox->insertItem(activeCodecListBox->text(i)); + availCodecListBox->setSelected( + availCodecListBox->count() - 1, true); + activeCodecListBox->removeItem(i); + return; + } + } +} + +void UserProfileForm::upCodec() { + QListBoxItem *lbi = activeCodecListBox->selectedItem(); + if (!lbi) return; + + int idx = activeCodecListBox->index(lbi); + if (idx == 0) return; + + QString label = lbi->text(); + activeCodecListBox->removeItem(idx); + activeCodecListBox->insertItem(label, idx - 1); + activeCodecListBox->setSelected(idx - 1, true); +} + +void UserProfileForm::downCodec() { + QListBoxItem *lbi = activeCodecListBox->selectedItem(); + if (!lbi) return; + + size_t idx = activeCodecListBox->index(lbi); + if (idx == activeCodecListBox->count() - 1) return; + + QString label = lbi->text(); + activeCodecListBox->removeItem(idx); + activeCodecListBox->insertItem(label, idx + 1); + activeCodecListBox->setSelected(idx + 1, true); +} + +void UserProfileForm::upConversion() { + QListViewItem *lvi = conversionListView->selectedItem(); + if (!lvi) return; + + QListViewItem *above = lvi->itemAbove(); + if (!above) return; + + QListViewItem *newAbove = above->itemAbove(); + + if (newAbove) { + lvi->moveItem(newAbove); + } else { + above->moveItem(lvi); + } + + lvi->setSelected(true); +} + +void UserProfileForm::downConversion() { + QListViewItem *lvi = conversionListView->selectedItem(); + if (!lvi) return; + + QListViewItem *below = lvi->itemBelow(); + if (!below) return; + + lvi->moveItem(below); + lvi->setSelected(true); +} + +void UserProfileForm::addConversion() { + QString expr; + QString replace; + + NumberConversionForm f; + if (f.exec(expr, replace) == QDialog::Accepted) { + QListViewItem *last = conversionListView->lastItem(); + if (last) { + new QListViewItem(conversionListView, last, expr, replace); + } else { + new QListViewItem(conversionListView, expr, replace); + } + } +} + +void UserProfileForm::editConversion() { + QListViewItem *lvi = conversionListView->selectedItem(); + if (!lvi) return; + + QString expr = lvi->text(colExpr); + QString replace = lvi->text(colReplace); + + NumberConversionForm f; + if (f.exec(expr, replace) == QDialog::Accepted) { + lvi->setText(colExpr, expr); + lvi->setText(colReplace, replace); + } +} + +void UserProfileForm::removeConversion() { + QListViewItem *lvi = conversionListView->selectedItem(); + if (!lvi) return; + delete lvi; +} + +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.ascii()).c_str(); + + // Remove special symbols + if (remove_special_phone_symbols && + looks_like_phone(number.ascii(), special_phone_symbols.ascii())) + { + number = remove_symbols( + number.ascii(), special_phone_symbols.ascii()).c_str(); + } + + QString msg = tr("%1 converts to %2") + .arg(number) + .arg(current_profile->convert_number(number.ascii(), get_number_conversions()).c_str()); + + ((t_gui *)ui)->cb_show_msg(this, msg.ascii(), 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); +} diff --git a/src/gui/wizardform.ui b/src/gui/wizardform.ui new file mode 100644 index 0000000..aa50690 --- /dev/null +++ b/src/gui/wizardform.ui @@ -0,0 +1,367 @@ + +WizardForm + + + WizardForm + + + + 0 + 0 + 596 + 321 + + + + Twinkle - Wizard + + + + unnamed + + + + layout15 + + + + unnamed + + + + stunServerLineEdit + + + The hostname, domain name or IP address of the STUN server. + + + + + stunServerTextLabel + + + S&TUN server: + + + stunServerLineEdit + + + + + usernameLineEdit + + + 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. + + + + + domainTextLabel + + + &Domain*: + + + domainLineEdit + + + + + serviceProviderComboBox + + + 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. + + + + + authNameTextLabel + + + &Authentication name: + + + authNameLineEdit + + + + + dislpayTextLabel + + + &Your name: + + + displayLineEdit + + + + + authNameLineEdit + + + Your SIP authentication name. Quite often this is the same as your SIP user name. It can be a different name though. + + + + + spacer22 + + + Horizontal + + + Expanding + + + + 206 + 20 + + + + + + domainLineEdit + + + 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. + + + + + displayLineEdit + + + 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. + + + + + proxyTextLabel + + + true + + + SIP pro&xy: + + + proxyLineEdit + + + + + proxyLineEdit + + + 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. + + + + + serviceProviderTextLabel + + + &SIP service provider: + + + serviceProviderComboBox + + + + + authPasswordTextLabel + + + &Password: + + + authPasswordLineEdit + + + + + usernameTextLabel + + + &User name*: + + + usernameLineEdit + + + + + authPasswordLineEdit + + + Password + + + Your password for authentication. + + + + + + + spacer23 + + + Vertical + + + Expanding + + + + 20 + 20 + + + + + + layout14 + + + + unnamed + + + + spacer20 + + + Horizontal + + + Expanding + + + + 371 + 20 + + + + + + okPushButton + + + &OK + + + Alt+O + + + true + + + + + cancelPushButton + + + &Cancel + + + Alt+C + + + + + + + + + okPushButton + clicked() + WizardForm + validate() + + + cancelPushButton + clicked() + WizardForm + reject() + + + usernameLineEdit + textChanged(const QString&) + WizardForm + updateAuthName(const QString&) + + + serviceProviderComboBox + activated(const QString&) + WizardForm + update(const QString&) + + + authNameLineEdit + lostFocus() + WizardForm + disableSuggestAuthName() + + + + serviceProviderComboBox + displayLineEdit + usernameLineEdit + domainLineEdit + authNameLineEdit + authPasswordLineEdit + proxyLineEdit + stunServerLineEdit + okPushButton + cancelPushButton + + + map + user.h + qregexp.h + qlineedit.h + qlabel.h + qvalidator.h + qcombobox.h + gui.h + qfile.h + wizardform.ui.h + + + struct t_provider; + + + bool suggestAuthName; + std::map<QString, t_provider> mapProviders; + t_user *user_config; + + + success() + + + initProviders() + exec( t_user * user ) + update( const QString & item ) + updateAuthName( const QString & s ) + disableSuggestAuthName() + validate() + + + init() + show( t_user * user ) + + + + diff --git a/src/gui/wizardform.ui.h b/src/gui/wizardform.ui.h new file mode 100644 index 0000000..2d0f652 --- /dev/null +++ b/src/gui/wizardform.ui.h @@ -0,0 +1,263 @@ +/**************************************************************************** +** 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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; +}; + +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->setCurrentItem(serviceProviderComboBox->count() - 1); + update(tr(PROV_OTHER)); +} + +void WizardForm::initProviders() +{ + serviceProviderComboBox->clear(); + serviceProviderComboBox->insertItem(tr(PROV_NONE)); + + QString fname = sys_config->get_dir_share().c_str(); + fname.append("/").append(FILE_PROVIDERS); + QFile providersFile(fname); + if (providersFile.open(IO_ReadOnly)) { + QTextStream providersStream(&providersFile); + QString entry; + while ((entry = providersStream.readLine()) != QString::null) { + // Skip comment + if (entry[0] == '#') continue; + + QStringList l = QStringList::split(";", entry, true); + + // 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->insertItem(l[0]); + } + providersFile.close(); + } + + serviceProviderComboBox->insertItem(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()); + setCaption(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()); + setCaption(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.").ascii(), + 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.").ascii(), + MSG_CRITICAL); + domainLineEdit->setFocus(); + return; + } + + // SIP proxy + if (proxyLineEdit->text() != "") { + s = USER_SCHEME; + s.append(':').append(proxyLineEdit->text()); + t_url u(s.ascii()); + if (!u.is_valid() || u.get_user() != "") { + ((t_gui *)ui)->cb_show_msg(this, tr("Invalid value for SIP proxy.").ascii(), + 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.ascii()); + if (!u.is_valid() || u.get_user() != "") { + ((t_gui *)ui)->cb_show_msg(this, tr("Invalid value for STUN server.").ascii(), + MSG_CRITICAL); + stunServerLineEdit->setFocus(); + stunServerLineEdit->selectAll(); + return; + } + } + + // Set all values in the user_config object + // USER + user_config->set_display(displayLineEdit->text().ascii()); + user_config->set_name(usernameLineEdit->text().ascii()); + user_config->set_domain(domainLineEdit->text().ascii()); + user_config->set_auth_name(authNameLineEdit->text().ascii()); + user_config->set_auth_pass(authPasswordLineEdit->text().ascii()); + + // 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.ascii())); + + // NAT + user_config->set_use_stun(!stunServerLineEdit->text().isEmpty()); + s = "stun:"; + s.append(stunServerLineEdit->text()); + user_config->set_stun_server(t_url(s.ascii())); + + // 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(); +} diff --git a/src/gui/yesnodialog.cpp b/src/gui/yesnodialog.cpp new file mode 100644 index 0000000..408f93e --- /dev/null +++ b/src/gui/yesnodialog.cpp @@ -0,0 +1,86 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "yesnodialog.h" + +#include "qlabel.h" +#include "qlayout.h" + +#include "userintf.h" + +// class YesNoDialog + +void YesNoDialog::actionYes() { + QDialog::accept(); +} + +void YesNoDialog::actionNo() { + QDialog::reject(); +} + +YesNoDialog::YesNoDialog() { + new QDialog(NULL, NULL, true, Qt::WDestructiveClose); +} + +YesNoDialog::YesNoDialog(QWidget *parent, const QString &caption, const QString &text) : + QDialog(parent, NULL, true, Qt::WDestructiveClose) +{ + setCaption(caption); + QBoxLayout *vb = new QVBoxLayout(this, 11, 6); + QLabel *lblQuestion = new QLabel(text, this); + vb->addWidget(lblQuestion); + QHBoxLayout *hb = new QHBoxLayout(NULL, 0, 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) +{} diff --git a/src/gui/yesnodialog.h b/src/gui/yesnodialog.h new file mode 100644 index 0000000..c196772 --- /dev/null +++ b/src/gui/yesnodialog.h @@ -0,0 +1,56 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/id_object.cpp b/src/id_object.cpp new file mode 100644 index 0000000..99499a6 --- /dev/null +++ b/src/id_object.cpp @@ -0,0 +1,41 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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(); +} diff --git a/src/id_object.h b/src/id_object.h new file mode 100644 index 0000000..c4e3f62 --- /dev/null +++ b/src/id_object.h @@ -0,0 +1,65 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/im/Makefile.am b/src/im/Makefile.am new file mode 100644 index 0000000..300a43a --- /dev/null +++ b/src/im/Makefile.am @@ -0,0 +1,9 @@ +AM_CPPFLAGS = -Wall $(CCRTP_CFLAGS) $(XML2_CFLAGS) -I$(top_srcdir)/src + +noinst_LIBRARIES = libim.a + +libim_a_SOURCES =\ + im_iscomposing_body.cpp\ + msg_session.cpp\ + im_iscomposing_body.h\ + msg_session.h diff --git a/src/im/im_iscomposing_body.cpp b/src/im/im_iscomposing_body.cpp new file mode 100644 index 0000000..a24fc6e --- /dev/null +++ b/src/im/im_iscomposing_body.cpp @@ -0,0 +1,182 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/im/im_iscomposing_body.h b/src/im/im_iscomposing_body.h new file mode 100644 index 0000000..57dbe62 --- /dev/null +++ b/src/im/im_iscomposing_body.h @@ -0,0 +1,89 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/im/msg_session.cpp b/src/im/msg_session.cpp new file mode 100644 index 0000000..640012f --- /dev/null +++ b/src/im/msg_session.cpp @@ -0,0 +1,423 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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); + } +} diff --git a/src/im/msg_session.h b/src/im/msg_session.h new file mode 100644 index 0000000..bf19d29 --- /dev/null +++ b/src/im/msg_session.h @@ -0,0 +1,347 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/line.cpp b/src/line.cpp new file mode 100644 index 0000000..d494262 --- /dev/null +++ b/src/line.cpp @@ -0,0 +1,2279 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include "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(); +} + +void t_call_info::clear(void) { + 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 { + 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.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; + + 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.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; + + 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; +} diff --git a/src/line.h b/src/line.h new file mode 100644 index 0000000..a1e1cb3 --- /dev/null +++ b/src/line.h @@ -0,0 +1,567 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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: + 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(); + 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 diff --git a/src/listener.cpp b/src/listener.cpp new file mode 100644 index 0000000..a16675d --- /dev/null +++ b/src/listener.cpp @@ -0,0 +1,616 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include "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; +} diff --git a/src/listener.h b/src/listener.h new file mode 100644 index 0000000..3aa6eb9 --- /dev/null +++ b/src/listener.h @@ -0,0 +1,36 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 index 0000000..ee2ce87 --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,372 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include "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); + } +} + +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); + thr_logview->detach(); + } + } else { + if (thr_logview) { + thr_logview->cancel(); + 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(); +} diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..6052aaf --- /dev/null +++ b/src/log.h @@ -0,0 +1,119 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..f5f8a18 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,606 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include "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; +} diff --git a/src/mwi/Makefile.am b/src/mwi/Makefile.am new file mode 100644 index 0000000..29415e6 --- /dev/null +++ b/src/mwi/Makefile.am @@ -0,0 +1,29 @@ +AM_CPPFLAGS = \ + -Wall \ + -I$(top_srcdir)/src \ + $(CCRTP_CFLAGS) \ + $(XML2_CFLAGS) + +#noinst_PROGRAMS = mwitest + +#mwitest_SOURCES = mwitest.cpp + +#mwitest_LDADD = $(top_builddir)/src/util.o\ +# $(top_builddir)/src/mwi/libmwi.a\ +# $(top_builddir)/src/sockets/libsocket.a\ +# $(top_builddir)/src/parser/libsipparser.a\ +# $(top_builddir)/src/sdp/libsdpparser.a\ +# -lresolv\ +# -lboost_regex + +noinst_LIBRARIES = libmwi.a + +libmwi_a_SOURCES =\ + mwi.cpp\ + mwi_dialog.cpp\ + mwi_subscription.cpp\ + simple_msg_sum_body.cpp\ + mwi.h\ + mwi_dialog.h\ + mwi_subscription.h\ + simple_msg_sum_body.h diff --git a/src/mwi/mwi.cpp b/src/mwi/mwi.cpp new file mode 100644 index 0000000..d889cd9 --- /dev/null +++ b/src/mwi/mwi.cpp @@ -0,0 +1,74 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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(); +} diff --git a/src/mwi/mwi.h b/src/mwi/mwi.h new file mode 100644 index 0000000..a6bd51b --- /dev/null +++ b/src/mwi/mwi.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/mwi/mwi_dialog.cpp b/src/mwi/mwi_dialog.cpp new file mode 100644 index 0000000..41ca0dd --- /dev/null +++ b/src/mwi/mwi_dialog.cpp @@ -0,0 +1,35 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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); +} diff --git a/src/mwi/mwi_dialog.h b/src/mwi/mwi_dialog.h new file mode 100644 index 0000000..73e2d91 --- /dev/null +++ b/src/mwi/mwi_dialog.h @@ -0,0 +1,35 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/mwi/mwi_subscription.cpp b/src/mwi/mwi_subscription.cpp new file mode 100644 index 0000000..795acd7 --- /dev/null +++ b/src/mwi/mwi_subscription.cpp @@ -0,0 +1,105 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/mwi/mwi_subscription.h b/src/mwi/mwi_subscription.h new file mode 100644 index 0000000..1abd8af --- /dev/null +++ b/src/mwi/mwi_subscription.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/mwi/simple_msg_sum_body.cpp b/src/mwi/simple_msg_sum_body.cpp new file mode 100644 index 0000000..33683f7 --- /dev/null +++ b/src/mwi/simple_msg_sum_body.cpp @@ -0,0 +1,186 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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 + boost::regex re("(\\d+)\\s*/\\s*(\\d+)(?:\\s*\\((\\d+)\\s*/\\s*(\\d+)\\s*\\))?"); + + boost::smatch m; + if (!boost::regex_match(s, m, re)) return false; + + if (m.size() == 3) { + newmsgs = strtoul(m.str(1).c_str(), NULL, 10); + oldmsgs = strtoul(m.str(2).c_str(), NULL, 10); + return true; + } else if (m.size() == 5) { + newmsgs = strtoul(m.str(1).c_str(), NULL, 10); + oldmsgs = strtoul(m.str(2).c_str(), NULL, 10); + newmsgs_urgent = strtoul(m.str(3).c_str(), NULL, 10); + oldmsgs_urgent = strtoul(m.str(4).c_str(), 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; +} diff --git a/src/mwi/simple_msg_sum_body.h b/src/mwi/simple_msg_sum_body.h new file mode 100644 index 0000000..48f7584 --- /dev/null +++ b/src/mwi/simple_msg_sum_body.h @@ -0,0 +1,103 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @file + * RFC 3842 simple-message-summary body + */ + +#ifndef SIMPLE_MSG_SUM_BODY_HH +#define SIMPLE_MSG_SUM_BODY_HH + +#include +#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 diff --git a/src/parser/Makefile.am b/src/parser/Makefile.am new file mode 100644 index 0000000..67bf122 --- /dev/null +++ b/src/parser/Makefile.am @@ -0,0 +1,195 @@ +AM_CPPFLAGS = -Wall $(XML2_CFLAGS) -I$(top_srcdir)/src + +# bison flags +AM_YFLAGS = -d + +# flex flags +# The -s flag is not used on purpose. As it is better to have +# some unmatched symbols echoed to stdout then a jammed flex +# causing the process to die. +AM_LFLAGS = -i + +# The output of bison cannot be compiled with the -O2 flag. +# With the -O2 flag g++ crashes. The -O2 flag is stripped from +# CXXFLAGS by configure. +# rumen: +# Above don't work. +# It is enought to drop -funit-at-a-time set when -O2 flag is set. +# The error is: +# "cc1plus: out of memory allocating 336746144 bytes after a total of 10846208 bytes." +# Note user can specify -O2 in CXXFLAGS at configure time ! +# Also note that unit-at-a-time if new for gcc 3.4 ! +# See http://lists.debian.org/debian-gcc/2006/07/msg00281.html +# that claim to be fixed in gcc-4.1+ . + +AM_CXXFLAGS = -fno-unit-at-a-time + + +# This target is only for testing the parser in isolation +#noinst_PROGRAMS = sipparse +# +#sipparse_SOURCES = main.cpp +# +#sipparse_LDADD = $(top_builddir)/src/util.o\ +# $(top_builddir)/src/parser/libsipparser.a\ +# $(top_builddir)/src/sdp/libsdpparser.a\ +# $(top_builddir)/src/sockets/libsocket.a\ +# -lresolv + +noinst_LIBRARIES = libsipparser.a + +libsipparser_a_SOURCES =\ + 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\ + parser.yxx\ + request.cpp\ + response.cpp\ + rijndael.cpp\ + route.cpp\ + scanner.lxx\ + sip_body.cpp\ + sip_message.cpp\ + challenge.h\ + coding.h\ + credentials.h\ + definitions.h\ + hdr_accept.h\ + hdr_accept_encoding.h\ + hdr_accept_language.h\ + hdr_alert_info.h\ + hdr_allow.h\ + hdr_allow_events.h\ + hdr_auth_info.h\ + hdr_authorization.h\ + hdr_call_id.h\ + hdr_call_info.h\ + hdr_contact.h\ + hdr_content_disp.h\ + hdr_content_encoding.h\ + hdr_content_language.h\ + hdr_content_length.h\ + hdr_content_type.h\ + hdr_cseq.h\ + hdr_date.h\ + hdr_error_info.h\ + hdr_event.h\ + hdr_expires.h\ + hdr_from.h\ + hdr_in_reply_to.h\ + hdr_max_forwards.h\ + hdr_min_expires.h\ + hdr_mime_version.h\ + hdr_organization.h\ + hdr_p_asserted_identity.h\ + hdr_p_preferred_identity.h\ + hdr_priority.h\ + hdr_privacy.h\ + hdr_proxy_authenticate.h\ + hdr_proxy_authorization.h\ + hdr_proxy_require.h\ + hdr_rack.h\ + hdr_record_route.h\ + hdr_refer_sub.h\ + hdr_refer_to.h\ + hdr_referred_by.h\ + hdr_replaces.h\ + hdr_reply_to.h\ + hdr_require.h\ + hdr_request_disposition.h\ + hdr_retry_after.h\ + hdr_route.h\ + hdr_rseq.h\ + hdr_server.h\ + hdr_service_route.h\ + hdr_sip_etag.h\ + hdr_sip_if_match.h\ + hdr_subject.h\ + hdr_subscription_state.h\ + hdr_supported.h\ + hdr_timestamp.h\ + hdr_to.h\ + hdr_unsupported.h\ + hdr_user_agent.h\ + hdr_via.h\ + hdr_warning.h\ + hdr_www_authenticate.h\ + header.h\ + identity.h\ + media_type.h\ + milenage.h\ + parameter.h\ + parse_ctrl.h\ + request.h\ + response.h\ + rijndael.h\ + route.h\ + sip_body.h\ + sip_message.h diff --git a/src/parser/challenge.cpp b/src/parser/challenge.cpp new file mode 100644 index 0000000..9c365a5 --- /dev/null +++ b/src/parser/challenge.cpp @@ -0,0 +1,178 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include "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 (auth_scheme == AUTH_DIGEST) { + 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; +} diff --git a/src/parser/challenge.h b/src/parser/challenge.h new file mode 100644 index 0000000..cc0c3bd --- /dev/null +++ b/src/parser/challenge.h @@ -0,0 +1,66 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/coding.cpp b/src/parser/coding.cpp new file mode 100644 index 0000000..83f8e8f --- /dev/null +++ b/src/parser/coding.cpp @@ -0,0 +1,42 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/coding.h b/src/parser/coding.h new file mode 100644 index 0000000..7169845 --- /dev/null +++ b/src/parser/coding.h @@ -0,0 +1,39 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/credentials.cpp b/src/parser/credentials.cpp new file mode 100644 index 0000000..067744c --- /dev/null +++ b/src/parser/credentials.cpp @@ -0,0 +1,158 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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 (auth_scheme == AUTH_DIGEST) { + 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; +} diff --git a/src/parser/credentials.h b/src/parser/credentials.h new file mode 100644 index 0000000..15fa881 --- /dev/null +++ b/src/parser/credentials.h @@ -0,0 +1,69 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/definitions.cpp b/src/parser/definitions.cpp new file mode 100644 index 0000000..c849553 --- /dev/null +++ b/src/parser/definitions.cpp @@ -0,0 +1,59 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include "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); + } +} + +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; +} diff --git a/src/parser/definitions.h b/src/parser/definitions.h new file mode 100644 index 0000000..af7a55a --- /dev/null +++ b/src/parser/definitions.h @@ -0,0 +1,81 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/parser/hdr_accept.cpp b/src/parser/hdr_accept.cpp new file mode 100644 index 0000000..31bf80e --- /dev/null +++ b/src/parser/hdr_accept.cpp @@ -0,0 +1,47 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_accept.h b/src/parser/hdr_accept.h new file mode 100644 index 0000000..feb90af --- /dev/null +++ b/src/parser/hdr_accept.h @@ -0,0 +1,44 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_accept_encoding.cpp b/src/parser/hdr_accept_encoding.cpp new file mode 100644 index 0000000..bd44104 --- /dev/null +++ b/src/parser/hdr_accept_encoding.cpp @@ -0,0 +1,42 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_accept_encoding.h b/src/parser/hdr_accept_encoding.h new file mode 100644 index 0000000..9c05d3c --- /dev/null +++ b/src/parser/hdr_accept_encoding.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_accept_language.cpp b/src/parser/hdr_accept_language.cpp new file mode 100644 index 0000000..2f38bb4 --- /dev/null +++ b/src/parser/hdr_accept_language.cpp @@ -0,0 +1,66 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_accept_language.h b/src/parser/hdr_accept_language.h new file mode 100644 index 0000000..e1cc85b --- /dev/null +++ b/src/parser/hdr_accept_language.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_alert_info.cpp b/src/parser/hdr_alert_info.cpp new file mode 100644 index 0000000..310d08b --- /dev/null +++ b/src/parser/hdr_alert_info.cpp @@ -0,0 +1,56 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_alert_info.h b/src/parser/hdr_alert_info.h new file mode 100644 index 0000000..d9f72ab --- /dev/null +++ b/src/parser/hdr_alert_info.h @@ -0,0 +1,53 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_allow.cpp b/src/parser/hdr_allow.cpp new file mode 100644 index 0000000..8031f50 --- /dev/null +++ b/src/parser/hdr_allow.cpp @@ -0,0 +1,75 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +#include "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; +} diff --git a/src/parser/hdr_allow.h b/src/parser/hdr_allow.h new file mode 100644 index 0000000..2aafbc9 --- /dev/null +++ b/src/parser/hdr_allow.h @@ -0,0 +1,45 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_allow_events.cpp b/src/parser/hdr_allow_events.cpp new file mode 100644 index 0000000..f88c42c --- /dev/null +++ b/src/parser/hdr_allow_events.cpp @@ -0,0 +1,42 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_allow_events.h b/src/parser/hdr_allow_events.h new file mode 100644 index 0000000..c0ee9a8 --- /dev/null +++ b/src/parser/hdr_allow_events.h @@ -0,0 +1,41 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_auth_info.cpp b/src/parser/hdr_auth_info.cpp new file mode 100644 index 0000000..7962432 --- /dev/null +++ b/src/parser/hdr_auth_info.cpp @@ -0,0 +1,99 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_auth_info.h b/src/parser/hdr_auth_info.h new file mode 100644 index 0000000..16fe87d --- /dev/null +++ b/src/parser/hdr_auth_info.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_authorization.cpp b/src/parser/hdr_authorization.cpp new file mode 100644 index 0000000..61ae723 --- /dev/null +++ b/src/parser/hdr_authorization.cpp @@ -0,0 +1,92 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; + } + } +} diff --git a/src/parser/hdr_authorization.h b/src/parser/hdr_authorization.h new file mode 100644 index 0000000..a3065bb --- /dev/null +++ b/src/parser/hdr_authorization.h @@ -0,0 +1,51 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 + diff --git a/src/parser/hdr_call_id.cpp b/src/parser/hdr_call_id.cpp new file mode 100644 index 0000000..8763f76 --- /dev/null +++ b/src/parser/hdr_call_id.cpp @@ -0,0 +1,33 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_call_id.h b/src/parser/hdr_call_id.h new file mode 100644 index 0000000..997d88f --- /dev/null +++ b/src/parser/hdr_call_id.h @@ -0,0 +1,38 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_call_info.cpp b/src/parser/hdr_call_info.cpp new file mode 100644 index 0000000..3646472 --- /dev/null +++ b/src/parser/hdr_call_info.cpp @@ -0,0 +1,56 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_call_info.h b/src/parser/hdr_call_info.h new file mode 100644 index 0000000..a6fd6cb --- /dev/null +++ b/src/parser/hdr_call_info.h @@ -0,0 +1,53 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_contact.cpp b/src/parser/hdr_contact.cpp new file mode 100644 index 0000000..00e8d5d --- /dev/null +++ b/src/parser/hdr_contact.cpp @@ -0,0 +1,178 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_contact.h b/src/parser/hdr_contact.h new file mode 100644 index 0000000..f1b74db --- /dev/null +++ b/src/parser/hdr_contact.h @@ -0,0 +1,100 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_content_disp.cpp b/src/parser/hdr_content_disp.cpp new file mode 100644 index 0000000..d74525d --- /dev/null +++ b/src/parser/hdr_content_disp.cpp @@ -0,0 +1,60 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_content_disp.h b/src/parser/hdr_content_disp.h new file mode 100644 index 0000000..81a8c34 --- /dev/null +++ b/src/parser/hdr_content_disp.h @@ -0,0 +1,51 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_content_encoding.cpp b/src/parser/hdr_content_encoding.cpp new file mode 100644 index 0000000..0c99854 --- /dev/null +++ b/src/parser/hdr_content_encoding.cpp @@ -0,0 +1,43 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_content_encoding.h b/src/parser/hdr_content_encoding.h new file mode 100644 index 0000000..2f6d726 --- /dev/null +++ b/src/parser/hdr_content_encoding.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_content_language.cpp b/src/parser/hdr_content_language.cpp new file mode 100644 index 0000000..6e0f23e --- /dev/null +++ b/src/parser/hdr_content_language.cpp @@ -0,0 +1,43 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_content_language.h b/src/parser/hdr_content_language.h new file mode 100644 index 0000000..738ab98 --- /dev/null +++ b/src/parser/hdr_content_language.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_content_length.cpp b/src/parser/hdr_content_length.cpp new file mode 100644 index 0000000..b1c952d --- /dev/null +++ b/src/parser/hdr_content_length.cpp @@ -0,0 +1,40 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_content_length.h b/src/parser/hdr_content_length.h new file mode 100644 index 0000000..accf8ca --- /dev/null +++ b/src/parser/hdr_content_length.h @@ -0,0 +1,39 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_content_type.cpp b/src/parser/hdr_content_type.cpp new file mode 100644 index 0000000..ba4fcfe --- /dev/null +++ b/src/parser/hdr_content_type.cpp @@ -0,0 +1,37 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_content_type.h b/src/parser/hdr_content_type.h new file mode 100644 index 0000000..059dcc8 --- /dev/null +++ b/src/parser/hdr_content_type.h @@ -0,0 +1,36 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_cseq.cpp b/src/parser/hdr_cseq.cpp new file mode 100644 index 0000000..06d096b --- /dev/null +++ b/src/parser/hdr_cseq.cpp @@ -0,0 +1,64 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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); +} diff --git a/src/parser/hdr_cseq.h b/src/parser/hdr_cseq.h new file mode 100644 index 0000000..87147a3 --- /dev/null +++ b/src/parser/hdr_cseq.h @@ -0,0 +1,46 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_date.cpp b/src/parser/hdr_date.cpp new file mode 100644 index 0000000..0ae5343 --- /dev/null +++ b/src/parser/hdr_date.cpp @@ -0,0 +1,64 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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; +} diff --git a/src/parser/hdr_date.h b/src/parser/hdr_date.h new file mode 100644 index 0000000..6d78009 --- /dev/null +++ b/src/parser/hdr_date.h @@ -0,0 +1,40 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_error_info.cpp b/src/parser/hdr_error_info.cpp new file mode 100644 index 0000000..3bd8be4 --- /dev/null +++ b/src/parser/hdr_error_info.cpp @@ -0,0 +1,56 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_error_info.h b/src/parser/hdr_error_info.h new file mode 100644 index 0000000..c0eb7a3 --- /dev/null +++ b/src/parser/hdr_error_info.h @@ -0,0 +1,53 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_event.cpp b/src/parser/hdr_event.cpp new file mode 100644 index 0000000..d0fe1a1 --- /dev/null +++ b/src/parser/hdr_event.cpp @@ -0,0 +1,54 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_event.h b/src/parser/hdr_event.h new file mode 100644 index 0000000..09c0ab9 --- /dev/null +++ b/src/parser/hdr_event.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_expires.cpp b/src/parser/hdr_expires.cpp new file mode 100644 index 0000000..0caeb04 --- /dev/null +++ b/src/parser/hdr_expires.cpp @@ -0,0 +1,36 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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); +} diff --git a/src/parser/hdr_expires.h b/src/parser/hdr_expires.h new file mode 100644 index 0000000..5ca589a --- /dev/null +++ b/src/parser/hdr_expires.h @@ -0,0 +1,39 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_from.cpp b/src/parser/hdr_from.cpp new file mode 100644 index 0000000..fa83456 --- /dev/null +++ b/src/parser/hdr_from.cpp @@ -0,0 +1,88 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; + } +} diff --git a/src/parser/hdr_from.h b/src/parser/hdr_from.h new file mode 100644 index 0000000..23fb7cf --- /dev/null +++ b/src/parser/hdr_from.h @@ -0,0 +1,58 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_in_reply_to.cpp b/src/parser/hdr_in_reply_to.cpp new file mode 100644 index 0000000..0383eed --- /dev/null +++ b/src/parser/hdr_in_reply_to.cpp @@ -0,0 +1,42 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_in_reply_to.h b/src/parser/hdr_in_reply_to.h new file mode 100644 index 0000000..774f0a1 --- /dev/null +++ b/src/parser/hdr_in_reply_to.h @@ -0,0 +1,39 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_max_forwards.cpp b/src/parser/hdr_max_forwards.cpp new file mode 100644 index 0000000..e4fd081 --- /dev/null +++ b/src/parser/hdr_max_forwards.cpp @@ -0,0 +1,36 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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); +} diff --git a/src/parser/hdr_max_forwards.h b/src/parser/hdr_max_forwards.h new file mode 100644 index 0000000..508309d --- /dev/null +++ b/src/parser/hdr_max_forwards.h @@ -0,0 +1,39 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_mime_version.cpp b/src/parser/hdr_mime_version.cpp new file mode 100644 index 0000000..48c5e50 --- /dev/null +++ b/src/parser/hdr_mime_version.cpp @@ -0,0 +1,33 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_mime_version.h b/src/parser/hdr_mime_version.h new file mode 100644 index 0000000..c25b9fc --- /dev/null +++ b/src/parser/hdr_mime_version.h @@ -0,0 +1,37 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_min_expires.cpp b/src/parser/hdr_min_expires.cpp new file mode 100644 index 0000000..7895c47 --- /dev/null +++ b/src/parser/hdr_min_expires.cpp @@ -0,0 +1,36 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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); +} diff --git a/src/parser/hdr_min_expires.h b/src/parser/hdr_min_expires.h new file mode 100644 index 0000000..ef82bc1 --- /dev/null +++ b/src/parser/hdr_min_expires.h @@ -0,0 +1,39 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_organization.cpp b/src/parser/hdr_organization.cpp new file mode 100644 index 0000000..fe32a72 --- /dev/null +++ b/src/parser/hdr_organization.cpp @@ -0,0 +1,33 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_organization.h b/src/parser/hdr_organization.h new file mode 100644 index 0000000..bfa788c --- /dev/null +++ b/src/parser/hdr_organization.h @@ -0,0 +1,37 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_p_asserted_identity.cpp b/src/parser/hdr_p_asserted_identity.cpp new file mode 100644 index 0000000..ac1847c --- /dev/null +++ b/src/parser/hdr_p_asserted_identity.cpp @@ -0,0 +1,43 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_p_asserted_identity.h b/src/parser/hdr_p_asserted_identity.h new file mode 100644 index 0000000..6148fac --- /dev/null +++ b/src/parser/hdr_p_asserted_identity.h @@ -0,0 +1,41 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_p_preferred_identity.cpp b/src/parser/hdr_p_preferred_identity.cpp new file mode 100644 index 0000000..0009fb6 --- /dev/null +++ b/src/parser/hdr_p_preferred_identity.cpp @@ -0,0 +1,43 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_p_preferred_identity.h b/src/parser/hdr_p_preferred_identity.h new file mode 100644 index 0000000..5449b61 --- /dev/null +++ b/src/parser/hdr_p_preferred_identity.h @@ -0,0 +1,41 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_priority.cpp b/src/parser/hdr_priority.cpp new file mode 100644 index 0000000..470ffac --- /dev/null +++ b/src/parser/hdr_priority.cpp @@ -0,0 +1,33 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_priority.h b/src/parser/hdr_priority.h new file mode 100644 index 0000000..5ba2128 --- /dev/null +++ b/src/parser/hdr_priority.h @@ -0,0 +1,37 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_privacy.cpp b/src/parser/hdr_privacy.cpp new file mode 100644 index 0000000..7c0cbca --- /dev/null +++ b/src/parser/hdr_privacy.cpp @@ -0,0 +1,51 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +#include "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; +} diff --git a/src/parser/hdr_privacy.h b/src/parser/hdr_privacy.h new file mode 100644 index 0000000..e7d33e2 --- /dev/null +++ b/src/parser/hdr_privacy.h @@ -0,0 +1,48 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// RFC 3323 +// Privacy header + +#ifndef _H_HDR_PRIVACY +#define _H_HDR_PRICACY + +#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 diff --git a/src/parser/hdr_proxy_authenticate.cpp b/src/parser/hdr_proxy_authenticate.cpp new file mode 100644 index 0000000..da1f0bd --- /dev/null +++ b/src/parser/hdr_proxy_authenticate.cpp @@ -0,0 +1,33 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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(); +} diff --git a/src/parser/hdr_proxy_authenticate.h b/src/parser/hdr_proxy_authenticate.h new file mode 100644 index 0000000..964c804 --- /dev/null +++ b/src/parser/hdr_proxy_authenticate.h @@ -0,0 +1,41 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_proxy_authorization.cpp b/src/parser/hdr_proxy_authorization.cpp new file mode 100644 index 0000000..2f73911 --- /dev/null +++ b/src/parser/hdr_proxy_authorization.cpp @@ -0,0 +1,92 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; + } + } +} diff --git a/src/parser/hdr_proxy_authorization.h b/src/parser/hdr_proxy_authorization.h new file mode 100644 index 0000000..b68849a --- /dev/null +++ b/src/parser/hdr_proxy_authorization.h @@ -0,0 +1,50 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_proxy_require.cpp b/src/parser/hdr_proxy_require.cpp new file mode 100644 index 0000000..70696fb --- /dev/null +++ b/src/parser/hdr_proxy_require.cpp @@ -0,0 +1,42 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_proxy_require.h b/src/parser/hdr_proxy_require.h new file mode 100644 index 0000000..a3bc594 --- /dev/null +++ b/src/parser/hdr_proxy_require.h @@ -0,0 +1,37 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_rack.cpp b/src/parser/hdr_rack.cpp new file mode 100644 index 0000000..24a8f15 --- /dev/null +++ b/src/parser/hdr_rack.cpp @@ -0,0 +1,64 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_rack.h b/src/parser/hdr_rack.h new file mode 100644 index 0000000..dbfb4ec --- /dev/null +++ b/src/parser/hdr_rack.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_record_route.cpp b/src/parser/hdr_record_route.cpp new file mode 100644 index 0000000..dc9f14d --- /dev/null +++ b/src/parser/hdr_record_route.cpp @@ -0,0 +1,66 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_record_route.h b/src/parser/hdr_record_route.h new file mode 100644 index 0000000..7123e83 --- /dev/null +++ b/src/parser/hdr_record_route.h @@ -0,0 +1,44 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_refer_sub.cpp b/src/parser/hdr_refer_sub.cpp new file mode 100644 index 0000000..9e3cad2 --- /dev/null +++ b/src/parser/hdr_refer_sub.cpp @@ -0,0 +1,49 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_refer_sub.h b/src/parser/hdr_refer_sub.h new file mode 100644 index 0000000..ba7fd5f --- /dev/null +++ b/src/parser/hdr_refer_sub.h @@ -0,0 +1,46 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_refer_to.cpp b/src/parser/hdr_refer_to.cpp new file mode 100644 index 0000000..ef9dc9a --- /dev/null +++ b/src/parser/hdr_refer_to.cpp @@ -0,0 +1,70 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_refer_to.h b/src/parser/hdr_refer_to.h new file mode 100644 index 0000000..41f1746 --- /dev/null +++ b/src/parser/hdr_refer_to.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_referred_by.cpp b/src/parser/hdr_referred_by.cpp new file mode 100644 index 0000000..aa4acf9 --- /dev/null +++ b/src/parser/hdr_referred_by.cpp @@ -0,0 +1,80 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_referred_by.h b/src/parser/hdr_referred_by.h new file mode 100644 index 0000000..c1133b8 --- /dev/null +++ b/src/parser/hdr_referred_by.h @@ -0,0 +1,49 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_replaces.cpp b/src/parser/hdr_replaces.cpp new file mode 100644 index 0000000..a7d7e1b --- /dev/null +++ b/src/parser/hdr_replaces.cpp @@ -0,0 +1,77 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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()); +} diff --git a/src/parser/hdr_replaces.h b/src/parser/hdr_replaces.h new file mode 100644 index 0000000..82acb47 --- /dev/null +++ b/src/parser/hdr_replaces.h @@ -0,0 +1,51 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_reply_to.cpp b/src/parser/hdr_reply_to.cpp new file mode 100644 index 0000000..4609f5e --- /dev/null +++ b/src/parser/hdr_reply_to.cpp @@ -0,0 +1,68 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_reply_to.h b/src/parser/hdr_reply_to.h new file mode 100644 index 0000000..bdf22f5 --- /dev/null +++ b/src/parser/hdr_reply_to.h @@ -0,0 +1,46 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_request_disposition.cpp b/src/parser/hdr_request_disposition.cpp new file mode 100644 index 0000000..0e4bec0 --- /dev/null +++ b/src/parser/hdr_request_disposition.cpp @@ -0,0 +1,213 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_request_disposition.h b/src/parser/hdr_request_disposition.h new file mode 100644 index 0000000..9ae0988 --- /dev/null +++ b/src/parser/hdr_request_disposition.h @@ -0,0 +1,111 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/parser/hdr_require.cpp b/src/parser/hdr_require.cpp new file mode 100644 index 0000000..adaf38c --- /dev/null +++ b/src/parser/hdr_require.cpp @@ -0,0 +1,75 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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(); +} diff --git a/src/parser/hdr_require.h b/src/parser/hdr_require.h new file mode 100644 index 0000000..463e8f3 --- /dev/null +++ b/src/parser/hdr_require.h @@ -0,0 +1,41 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_retry_after.cpp b/src/parser/hdr_retry_after.cpp new file mode 100644 index 0000000..ee54ea3 --- /dev/null +++ b/src/parser/hdr_retry_after.cpp @@ -0,0 +1,69 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_retry_after.h b/src/parser/hdr_retry_after.h new file mode 100644 index 0000000..759a513 --- /dev/null +++ b/src/parser/hdr_retry_after.h @@ -0,0 +1,46 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_route.cpp b/src/parser/hdr_route.cpp new file mode 100644 index 0000000..8984574 --- /dev/null +++ b/src/parser/hdr_route.cpp @@ -0,0 +1,67 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_route.h b/src/parser/hdr_route.h new file mode 100644 index 0000000..a631403 --- /dev/null +++ b/src/parser/hdr_route.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_rseq.cpp b/src/parser/hdr_rseq.cpp new file mode 100644 index 0000000..78fcf98 --- /dev/null +++ b/src/parser/hdr_rseq.cpp @@ -0,0 +1,40 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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); +} diff --git a/src/parser/hdr_rseq.h b/src/parser/hdr_rseq.h new file mode 100644 index 0000000..601a605 --- /dev/null +++ b/src/parser/hdr_rseq.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_server.cpp b/src/parser/hdr_server.cpp new file mode 100644 index 0000000..6f59e01 --- /dev/null +++ b/src/parser/hdr_server.cpp @@ -0,0 +1,77 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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(); +} diff --git a/src/parser/hdr_server.h b/src/parser/hdr_server.h new file mode 100644 index 0000000..cfe9a8f --- /dev/null +++ b/src/parser/hdr_server.h @@ -0,0 +1,55 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_service_route.cpp b/src/parser/hdr_service_route.cpp new file mode 100644 index 0000000..6b0c42c --- /dev/null +++ b/src/parser/hdr_service_route.cpp @@ -0,0 +1,66 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_service_route.h b/src/parser/hdr_service_route.h new file mode 100644 index 0000000..0c5a016 --- /dev/null +++ b/src/parser/hdr_service_route.h @@ -0,0 +1,44 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_sip_etag.cpp b/src/parser/hdr_sip_etag.cpp new file mode 100644 index 0000000..17ac857 --- /dev/null +++ b/src/parser/hdr_sip_etag.cpp @@ -0,0 +1,31 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_sip_etag.h b/src/parser/hdr_sip_etag.h new file mode 100644 index 0000000..de352e9 --- /dev/null +++ b/src/parser/hdr_sip_etag.h @@ -0,0 +1,49 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/parser/hdr_sip_if_match.cpp b/src/parser/hdr_sip_if_match.cpp new file mode 100644 index 0000000..aef4241 --- /dev/null +++ b/src/parser/hdr_sip_if_match.cpp @@ -0,0 +1,36 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_sip_if_match.h b/src/parser/hdr_sip_if_match.h new file mode 100644 index 0000000..67e98f4 --- /dev/null +++ b/src/parser/hdr_sip_if_match.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/parser/hdr_subject.cpp b/src/parser/hdr_subject.cpp new file mode 100644 index 0000000..3a2094d --- /dev/null +++ b/src/parser/hdr_subject.cpp @@ -0,0 +1,34 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_subject.h b/src/parser/hdr_subject.h new file mode 100644 index 0000000..130614a --- /dev/null +++ b/src/parser/hdr_subject.h @@ -0,0 +1,37 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_subscription_state.cpp b/src/parser/hdr_subscription_state.cpp new file mode 100644 index 0000000..2fb4354 --- /dev/null +++ b/src/parser/hdr_subscription_state.cpp @@ -0,0 +1,78 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_subscription_state.h b/src/parser/hdr_subscription_state.h new file mode 100644 index 0000000..b0837d4 --- /dev/null +++ b/src/parser/hdr_subscription_state.h @@ -0,0 +1,63 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_supported.cpp b/src/parser/hdr_supported.cpp new file mode 100644 index 0000000..96b6a42 --- /dev/null +++ b/src/parser/hdr_supported.cpp @@ -0,0 +1,72 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_supported.h b/src/parser/hdr_supported.h new file mode 100644 index 0000000..aa012f1 --- /dev/null +++ b/src/parser/hdr_supported.h @@ -0,0 +1,49 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_timestamp.cpp b/src/parser/hdr_timestamp.cpp new file mode 100644 index 0000000..7e1f769 --- /dev/null +++ b/src/parser/hdr_timestamp.cpp @@ -0,0 +1,51 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_timestamp.h b/src/parser/hdr_timestamp.h new file mode 100644 index 0000000..9a1780a --- /dev/null +++ b/src/parser/hdr_timestamp.h @@ -0,0 +1,40 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_to.cpp b/src/parser/hdr_to.cpp new file mode 100644 index 0000000..06ee170 --- /dev/null +++ b/src/parser/hdr_to.cpp @@ -0,0 +1,80 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_to.h b/src/parser/hdr_to.h new file mode 100644 index 0000000..3a308ba --- /dev/null +++ b/src/parser/hdr_to.h @@ -0,0 +1,48 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_unsupported.cpp b/src/parser/hdr_unsupported.cpp new file mode 100644 index 0000000..2107fbc --- /dev/null +++ b/src/parser/hdr_unsupported.cpp @@ -0,0 +1,59 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_unsupported.h b/src/parser/hdr_unsupported.h new file mode 100644 index 0000000..956d2b4 --- /dev/null +++ b/src/parser/hdr_unsupported.h @@ -0,0 +1,39 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_user_agent.cpp b/src/parser/hdr_user_agent.cpp new file mode 100644 index 0000000..0c36f7f --- /dev/null +++ b/src/parser/hdr_user_agent.cpp @@ -0,0 +1,47 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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(); +} diff --git a/src/parser/hdr_user_agent.h b/src/parser/hdr_user_agent.h new file mode 100644 index 0000000..b67a20e --- /dev/null +++ b/src/parser/hdr_user_agent.h @@ -0,0 +1,44 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_via.cpp b/src/parser/hdr_via.cpp new file mode 100644 index 0000000..d076bfe --- /dev/null +++ b/src/parser/hdr_via.cpp @@ -0,0 +1,211 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include "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); +} diff --git a/src/parser/hdr_via.h b/src/parser/hdr_via.h new file mode 100644 index 0000000..e7e6260 --- /dev/null +++ b/src/parser/hdr_via.h @@ -0,0 +1,73 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/hdr_warning.cpp b/src/parser/hdr_warning.cpp new file mode 100644 index 0000000..e77ef35 --- /dev/null +++ b/src/parser/hdr_warning.cpp @@ -0,0 +1,89 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/hdr_warning.h b/src/parser/hdr_warning.h new file mode 100644 index 0000000..1bfa200 --- /dev/null +++ b/src/parser/hdr_warning.h @@ -0,0 +1,83 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 bandwith 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 bandwith" +#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 diff --git a/src/parser/hdr_www_authenticate.cpp b/src/parser/hdr_www_authenticate.cpp new file mode 100644 index 0000000..c5d3286 --- /dev/null +++ b/src/parser/hdr_www_authenticate.cpp @@ -0,0 +1,33 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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(); +} diff --git a/src/parser/hdr_www_authenticate.h b/src/parser/hdr_www_authenticate.h new file mode 100644 index 0000000..e0d4dd2 --- /dev/null +++ b/src/parser/hdr_www_authenticate.h @@ -0,0 +1,41 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/header.cpp b/src/parser/header.cpp new file mode 100644 index 0000000..622e9a2 --- /dev/null +++ b/src/parser/header.cpp @@ -0,0 +1,82 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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)); +} diff --git a/src/parser/header.h b/src/parser/header.h new file mode 100644 index 0000000..5d6f572 --- /dev/null +++ b/src/parser/header.h @@ -0,0 +1,66 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/identity.cpp b/src/parser/identity.cpp new file mode 100644 index 0000000..161a44a --- /dev/null +++ b/src/parser/identity.cpp @@ -0,0 +1,51 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/identity.h b/src/parser/identity.h new file mode 100644 index 0000000..6da7d72 --- /dev/null +++ b/src/parser/identity.h @@ -0,0 +1,40 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/parser/media_type.cpp b/src/parser/media_type.cpp new file mode 100644 index 0000000..c9112df --- /dev/null +++ b/src/parser/media_type.cpp @@ -0,0 +1,100 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include + +#include "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; +} diff --git a/src/parser/media_type.h b/src/parser/media_type.h new file mode 100644 index 0000000..5c04c5e --- /dev/null +++ b/src/parser/media_type.h @@ -0,0 +1,84 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/parser/milenage.cpp b/src/parser/milenage.cpp new file mode 100644 index 0000000..66dae9b --- /dev/null +++ b/src/parser/milenage.cpp @@ -0,0 +1,284 @@ +/*------------------------------------------------------------------- + * 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 */ diff --git a/src/parser/milenage.h b/src/parser/milenage.h new file mode 100644 index 0000000..a5f407f --- /dev/null +++ b/src/parser/milenage.h @@ -0,0 +1,35 @@ +/*------------------------------------------------------------------- + * 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 diff --git a/src/parser/parameter.cpp b/src/parser/parameter.cpp new file mode 100644 index 0000000..ef5e9de --- /dev/null +++ b/src/parser/parameter.cpp @@ -0,0 +1,90 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/parameter.h b/src/parser/parameter.h new file mode 100644 index 0000000..6ef903f --- /dev/null +++ b/src/parser/parameter.h @@ -0,0 +1,60 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/parser/parse_ctrl.cpp b/src/parser/parse_ctrl.cpp new file mode 100644 index 0000000..cfaad81 --- /dev/null +++ b/src/parser/parse_ctrl.cpp @@ -0,0 +1,136 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/parse_ctrl.h b/src/parser/parse_ctrl.h new file mode 100644 index 0000000..5270edb --- /dev/null +++ b/src/parser/parse_ctrl.h @@ -0,0 +1,131 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/parser.cxx b/src/parser/parser.cxx new file mode 100644 index 0000000..f2aae95 --- /dev/null +++ b/src/parser/parser.cxx @@ -0,0 +1,5106 @@ +/* A Bison parser, made by GNU Bison 2.3. */ + +/* Skeleton implementation 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* 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. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "2.3" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 0 + +/* Using locations. */ +#define YYLSP_NEEDED 0 + + + +/* 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_QSTRING = 260, + T_COMMENT = 261, + T_LINE = 262, + T_URI = 263, + T_URI_WILDCARD = 264, + T_DISPLAY = 265, + T_LANG = 266, + T_WORD = 267, + T_WKDAY = 268, + T_MONTH = 269, + T_GMT = 270, + T_SIP = 271, + T_METHOD = 272, + T_AUTH_DIGEST = 273, + T_AUTH_OTHER = 274, + T_IPV6ADDR = 275, + T_PARAMVAL = 276, + T_HDR_ACCEPT = 277, + T_HDR_ACCEPT_ENCODING = 278, + T_HDR_ACCEPT_LANGUAGE = 279, + T_HDR_ALERT_INFO = 280, + T_HDR_ALLOW = 281, + T_HDR_ALLOW_EVENTS = 282, + T_HDR_AUTHENTICATION_INFO = 283, + T_HDR_AUTHORIZATION = 284, + T_HDR_CALL_ID = 285, + T_HDR_CALL_INFO = 286, + T_HDR_CONTACT = 287, + T_HDR_CONTENT_DISP = 288, + T_HDR_CONTENT_ENCODING = 289, + T_HDR_CONTENT_LANGUAGE = 290, + T_HDR_CONTENT_LENGTH = 291, + T_HDR_CONTENT_TYPE = 292, + T_HDR_CSEQ = 293, + T_HDR_DATE = 294, + T_HDR_ERROR_INFO = 295, + T_HDR_EVENT = 296, + T_HDR_EXPIRES = 297, + T_HDR_FROM = 298, + T_HDR_IN_REPLY_TO = 299, + T_HDR_MAX_FORWARDS = 300, + T_HDR_MIN_EXPIRES = 301, + T_HDR_MIME_VERSION = 302, + T_HDR_ORGANIZATION = 303, + T_HDR_P_ASSERTED_IDENTITY = 304, + T_HDR_P_PREFERRED_IDENTITY = 305, + T_HDR_PRIORITY = 306, + T_HDR_PRIVACY = 307, + T_HDR_PROXY_AUTHENTICATE = 308, + T_HDR_PROXY_AUTHORIZATION = 309, + T_HDR_PROXY_REQUIRE = 310, + T_HDR_RACK = 311, + T_HDR_RECORD_ROUTE = 312, + T_HDR_SERVICE_ROUTE = 313, + T_HDR_REFER_SUB = 314, + T_HDR_REFER_TO = 315, + T_HDR_REFERRED_BY = 316, + T_HDR_REPLACES = 317, + T_HDR_REPLY_TO = 318, + T_HDR_REQUIRE = 319, + T_HDR_REQUEST_DISPOSITION = 320, + T_HDR_RETRY_AFTER = 321, + T_HDR_ROUTE = 322, + T_HDR_RSEQ = 323, + T_HDR_SERVER = 324, + T_HDR_SIP_ETAG = 325, + T_HDR_SIP_IF_MATCH = 326, + T_HDR_SUBJECT = 327, + T_HDR_SUBSCRIPTION_STATE = 328, + T_HDR_SUPPORTED = 329, + T_HDR_TIMESTAMP = 330, + T_HDR_TO = 331, + T_HDR_UNSUPPORTED = 332, + T_HDR_USER_AGENT = 333, + T_HDR_VIA = 334, + T_HDR_WARNING = 335, + T_HDR_WWW_AUTHENTICATE = 336, + T_HDR_UNKNOWN = 337, + T_CRLF = 338, + T_ERROR = 339, + T_NULL = 340 + }; +#endif +/* Tokens. */ +#define T_NUM 258 +#define T_TOKEN 259 +#define T_QSTRING 260 +#define T_COMMENT 261 +#define T_LINE 262 +#define T_URI 263 +#define T_URI_WILDCARD 264 +#define T_DISPLAY 265 +#define T_LANG 266 +#define T_WORD 267 +#define T_WKDAY 268 +#define T_MONTH 269 +#define T_GMT 270 +#define T_SIP 271 +#define T_METHOD 272 +#define T_AUTH_DIGEST 273 +#define T_AUTH_OTHER 274 +#define T_IPV6ADDR 275 +#define T_PARAMVAL 276 +#define T_HDR_ACCEPT 277 +#define T_HDR_ACCEPT_ENCODING 278 +#define T_HDR_ACCEPT_LANGUAGE 279 +#define T_HDR_ALERT_INFO 280 +#define T_HDR_ALLOW 281 +#define T_HDR_ALLOW_EVENTS 282 +#define T_HDR_AUTHENTICATION_INFO 283 +#define T_HDR_AUTHORIZATION 284 +#define T_HDR_CALL_ID 285 +#define T_HDR_CALL_INFO 286 +#define T_HDR_CONTACT 287 +#define T_HDR_CONTENT_DISP 288 +#define T_HDR_CONTENT_ENCODING 289 +#define T_HDR_CONTENT_LANGUAGE 290 +#define T_HDR_CONTENT_LENGTH 291 +#define T_HDR_CONTENT_TYPE 292 +#define T_HDR_CSEQ 293 +#define T_HDR_DATE 294 +#define T_HDR_ERROR_INFO 295 +#define T_HDR_EVENT 296 +#define T_HDR_EXPIRES 297 +#define T_HDR_FROM 298 +#define T_HDR_IN_REPLY_TO 299 +#define T_HDR_MAX_FORWARDS 300 +#define T_HDR_MIN_EXPIRES 301 +#define T_HDR_MIME_VERSION 302 +#define T_HDR_ORGANIZATION 303 +#define T_HDR_P_ASSERTED_IDENTITY 304 +#define T_HDR_P_PREFERRED_IDENTITY 305 +#define T_HDR_PRIORITY 306 +#define T_HDR_PRIVACY 307 +#define T_HDR_PROXY_AUTHENTICATE 308 +#define T_HDR_PROXY_AUTHORIZATION 309 +#define T_HDR_PROXY_REQUIRE 310 +#define T_HDR_RACK 311 +#define T_HDR_RECORD_ROUTE 312 +#define T_HDR_SERVICE_ROUTE 313 +#define T_HDR_REFER_SUB 314 +#define T_HDR_REFER_TO 315 +#define T_HDR_REFERRED_BY 316 +#define T_HDR_REPLACES 317 +#define T_HDR_REPLY_TO 318 +#define T_HDR_REQUIRE 319 +#define T_HDR_REQUEST_DISPOSITION 320 +#define T_HDR_RETRY_AFTER 321 +#define T_HDR_ROUTE 322 +#define T_HDR_RSEQ 323 +#define T_HDR_SERVER 324 +#define T_HDR_SIP_ETAG 325 +#define T_HDR_SIP_IF_MATCH 326 +#define T_HDR_SUBJECT 327 +#define T_HDR_SUBSCRIPTION_STATE 328 +#define T_HDR_SUPPORTED 329 +#define T_HDR_TIMESTAMP 330 +#define T_HDR_TO 331 +#define T_HDR_UNSUPPORTED 332 +#define T_HDR_USER_AGENT 333 +#define T_HDR_VIA 334 +#define T_HDR_WARNING 335 +#define T_HDR_WWW_AUTHENTICATE 336 +#define T_HDR_UNKNOWN 337 +#define T_CRLF 338 +#define T_ERROR 339 +#define T_NULL 340 + + + + +/* Copy the first part of user declarations. */ +#line 19 "parser.yxx" + +#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); + + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 0 +#endif + +/* Enabling the token table. */ +#ifndef YYTOKEN_TABLE +# define YYTOKEN_TABLE 0 +#endif + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE +#line 49 "parser.yxx" +{ + 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; +} +/* Line 193 of yacc.c. */ +#line 310 "parser.cxx" + YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + + + +/* Copy the second part of user declarations. */ + + +/* Line 216 of yacc.c. */ +#line 323 "parser.cxx" + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#elif (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +typedef signed char yytype_int8; +#else +typedef short int yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(msgid) dgettext ("bison-runtime", msgid) +# endif +# endif +# ifndef YY_ +# define YY_(msgid) msgid +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(e) ((void) (e)) +#else +# define YYUSE(e) /* empty */ +#endif + +/* Identity function, used to suppress warnings about constant conditions. */ +#ifndef lint +# define YYID(n) (n) +#else +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static int +YYID (int i) +#else +static int +YYID (i) + int i; +#endif +{ + return i; +} +#endif + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0)) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined _STDLIB_H \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss; + YYSTYPE yyvs; + }; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \ + + YYSTACK_GAP_MAXIMUM) + +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (YYID (0)) +# endif +# endif + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack, Stack, yysize); \ + Stack = &yyptr->Stack; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (YYID (0)) + +#endif + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 3 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 734 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 99 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 254 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 432 +/* YYNRULES -- Number of states. */ +#define YYNSTATES 791 + +/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 340 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +static const yytype_uint8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 94, 95, 2, 2, 88, 2, 96, 86, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 87, 89, + 91, 90, 92, 2, 93, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 97, 2, 98, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, + 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, + 85 +}; + +#if YYDEBUG +/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ +static const yytype_uint16 yyprhs[] = +{ + 0, 0, 3, 4, 7, 9, 11, 14, 18, 19, + 20, 27, 28, 33, 37, 38, 39, 40, 48, 49, + 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, + 92, 96, 100, 104, 108, 112, 116, 120, 124, 128, + 132, 136, 140, 144, 148, 152, 156, 160, 164, 168, + 172, 176, 180, 184, 188, 192, 196, 200, 204, 208, + 212, 216, 220, 224, 228, 232, 236, 240, 244, 248, + 252, 256, 260, 264, 268, 272, 276, 280, 284, 288, + 292, 297, 301, 305, 309, 313, 317, 321, 325, 329, + 333, 337, 341, 345, 349, 353, 357, 361, 365, 369, + 373, 377, 381, 385, 389, 393, 397, 401, 405, 409, + 413, 417, 421, 425, 429, 433, 437, 441, 445, 449, + 453, 457, 461, 465, 469, 473, 477, 481, 485, 489, + 493, 497, 501, 505, 509, 513, 517, 521, 525, 529, + 533, 537, 540, 543, 546, 549, 552, 555, 558, 561, + 564, 567, 570, 573, 576, 579, 582, 585, 588, 591, + 594, 597, 600, 603, 606, 609, 612, 615, 618, 621, + 624, 627, 630, 633, 636, 639, 642, 645, 648, 651, + 654, 657, 660, 663, 666, 669, 672, 675, 678, 681, + 684, 687, 690, 693, 696, 699, 702, 705, 708, 711, + 714, 717, 718, 721, 726, 730, 731, 735, 737, 738, + 739, 745, 747, 749, 751, 755, 757, 760, 763, 764, + 767, 768, 773, 775, 776, 780, 782, 786, 787, 788, + 795, 797, 801, 802, 803, 807, 809, 813, 815, 819, + 820, 821, 828, 829, 830, 834, 836, 838, 842, 845, + 846, 847, 851, 852, 853, 854, 862, 863, 865, 867, + 870, 872, 876, 877, 880, 881, 886, 887, 888, 892, + 895, 896, 897, 902, 903, 904, 918, 920, 924, 925, + 926, 933, 934, 935, 939, 940, 944, 945, 948, 949, + 950, 957, 958, 959, 963, 964, 965, 971, 972, 973, + 977, 978, 979, 983, 985, 986, 987, 991, 992, 995, + 999, 1000, 1003, 1007, 1009, 1011, 1015, 1017, 1021, 1023, + 1027, 1028, 1029, 1037, 1039, 1043, 1044, 1045, 1050, 1051, + 1055, 1057, 1061, 1062, 1063, 1069, 1070, 1071, 1072, 1078, + 1080, 1084, 1086, 1089, 1091, 1094, 1099, 1100, 1101, 1105, + 1106, 1108, 1112, 1113, 1116, 1118, 1121, 1123, 1127, 1129, + 1133, 1134, 1138, 1140, 1144, 1146, 1149, 1151, 1155, 1159, + 1165, 1167, 1168, 1169, 1175, 1177, 1178, 1179, 1185, 1186, + 1187, 1193, 1195, 1199, 1200, 1201, 1207, 1208, 1209, 1213, + 1215, 1217, 1221, 1223, 1227, 1229, 1233, 1234, 1238, 1239, + 1243, 1244, 1247, 1249, 1253, 1254, 1258, 1259, 1263, 1264, + 1267, 1268, 1271, 1272, 1275, 1276, 1277, 1281, 1282, 1283, + 1289, 1292, 1294, 1298, 1301, 1302, 1306, 1307, 1311, 1314, + 1316, 1318, 1320 +}; + +/* YYRHS -- A `-1'-separated list of the rules' RHS. */ +static const yytype_int16 yyrhs[] = +{ + 100, 0, -1, -1, 101, 102, -1, 103, -1, 109, + -1, 1, 85, -1, 104, 114, 83, -1, -1, -1, + 17, 105, 8, 106, 107, 83, -1, -1, 16, 108, + 86, 4, -1, 110, 114, 83, -1, -1, -1, -1, + 107, 111, 3, 112, 7, 113, 83, -1, -1, 114, + 115, -1, 116, 176, 83, -1, 117, 183, 83, -1, + 118, 186, 83, -1, 119, 191, 83, -1, 120, 195, + 83, -1, 121, 343, 83, -1, 122, 318, 83, -1, + 123, 324, 83, -1, 124, 196, 83, -1, 125, 200, + 83, -1, 126, 204, 83, -1, 127, 216, 83, -1, + 128, 217, 83, -1, 129, 218, 83, -1, 130, 221, + 83, -1, 131, 224, 83, -1, 132, 225, 83, -1, + 133, 228, 83, -1, 135, 342, 83, -1, 134, 231, + 83, -1, 136, 235, 83, -1, 137, 238, 83, -1, + 138, 244, 83, -1, 139, 249, 83, -1, 140, 252, + 83, -1, 141, 255, 83, -1, 142, 256, 83, -1, + 143, 259, 83, -1, 144, 261, 83, -1, 145, 263, + 83, -1, 146, 264, 83, -1, 147, 330, 83, -1, + 148, 332, 83, -1, 149, 265, 83, -1, 150, 339, + 83, -1, 151, 266, 83, -1, 152, 349, 83, -1, + 153, 345, 83, -1, 154, 347, 83, -1, 155, 271, + 83, -1, 156, 274, 83, -1, 157, 276, 83, -1, + 158, 352, 83, -1, 159, 277, 83, -1, 160, 283, + 83, -1, 161, 336, 83, -1, 162, 284, 83, -1, + 163, 270, 83, -1, 164, 350, 83, -1, 165, 351, + 83, -1, 166, 286, 83, -1, 167, 344, 83, -1, + 168, 289, 83, -1, 169, 290, 83, -1, 170, 295, + 83, -1, 171, 297, 83, -1, 172, 298, 83, -1, + 173, 299, 83, -1, 174, 310, 83, -1, 175, 334, + 83, -1, 82, 87, 314, 83, -1, 116, 1, 83, + -1, 117, 1, 83, -1, 118, 1, 83, -1, 119, + 1, 83, -1, 120, 1, 83, -1, 121, 1, 83, + -1, 122, 1, 83, -1, 123, 1, 83, -1, 124, + 1, 83, -1, 125, 1, 83, -1, 126, 1, 83, + -1, 127, 1, 83, -1, 128, 1, 83, -1, 129, + 1, 83, -1, 130, 1, 83, -1, 131, 1, 83, + -1, 132, 1, 83, -1, 133, 1, 83, -1, 134, + 1, 83, -1, 135, 1, 83, -1, 136, 1, 83, + -1, 137, 1, 83, -1, 138, 1, 83, -1, 139, + 1, 83, -1, 140, 1, 83, -1, 141, 1, 83, + -1, 142, 1, 83, -1, 143, 1, 83, -1, 144, + 1, 83, -1, 145, 1, 83, -1, 146, 1, 83, + -1, 147, 1, 83, -1, 148, 1, 83, -1, 149, + 1, 83, -1, 150, 1, 83, -1, 151, 1, 83, + -1, 152, 1, 83, -1, 153, 1, 83, -1, 154, + 1, 83, -1, 155, 1, 83, -1, 156, 1, 83, + -1, 157, 1, 83, -1, 158, 1, 83, -1, 159, + 1, 83, -1, 160, 1, 83, -1, 161, 1, 83, + -1, 162, 1, 83, -1, 163, 1, 83, -1, 164, + 1, 83, -1, 165, 1, 83, -1, 166, 1, 83, + -1, 167, 1, 83, -1, 168, 1, 83, -1, 169, + 1, 83, -1, 170, 1, 83, -1, 171, 1, 83, + -1, 172, 1, 83, -1, 173, 1, 83, -1, 174, + 1, 83, -1, 175, 1, 83, -1, 22, 87, -1, + 23, 87, -1, 24, 87, -1, 25, 87, -1, 26, + 87, -1, 27, 87, -1, 28, 87, -1, 29, 87, + -1, 30, 87, -1, 31, 87, -1, 32, 87, -1, + 33, 87, -1, 34, 87, -1, 35, 87, -1, 36, + 87, -1, 37, 87, -1, 38, 87, -1, 39, 87, + -1, 40, 87, -1, 41, 87, -1, 42, 87, -1, + 43, 87, -1, 44, 87, -1, 45, 87, -1, 46, + 87, -1, 47, 87, -1, 48, 87, -1, 49, 87, + -1, 50, 87, -1, 51, 87, -1, 52, 87, -1, + 53, 87, -1, 54, 87, -1, 55, 87, -1, 56, + 87, -1, 57, 87, -1, 59, 87, -1, 60, 87, + -1, 61, 87, -1, 62, 87, -1, 63, 87, -1, + 64, 87, -1, 65, 87, -1, 66, 87, -1, 67, + 87, -1, 68, 87, -1, 69, 87, -1, 58, 87, + -1, 70, 87, -1, 71, 87, -1, 72, 87, -1, + 73, 87, -1, 74, 87, -1, 75, 87, -1, 76, + 87, -1, 77, 87, -1, 78, 87, -1, 79, 87, + -1, 80, 87, -1, 81, 87, -1, -1, 177, 178, + -1, 176, 88, 177, 178, -1, 4, 86, 4, -1, + -1, 178, 89, 179, -1, 4, -1, -1, -1, 4, + 90, 180, 182, 181, -1, 21, -1, 5, -1, 184, + -1, 183, 88, 184, -1, 4, -1, 4, 185, -1, + 89, 179, -1, -1, 187, 189, -1, -1, 186, 88, + 188, 189, -1, 11, -1, -1, 11, 190, 185, -1, + 192, -1, 191, 88, 192, -1, -1, -1, 91, 193, + 8, 194, 92, 178, -1, 4, -1, 195, 88, 4, + -1, -1, -1, 197, 199, 198, -1, 12, -1, 12, + 93, 12, -1, 201, -1, 200, 88, 201, -1, -1, + -1, 91, 202, 8, 203, 92, 178, -1, -1, -1, + 205, 9, 206, -1, 207, -1, 208, -1, 207, 88, + 208, -1, 209, 178, -1, -1, -1, 210, 8, 211, + -1, -1, -1, -1, 212, 215, 91, 213, 8, 214, + 92, -1, -1, 10, -1, 5, -1, 4, 178, -1, + 184, -1, 217, 88, 184, -1, -1, 219, 189, -1, + -1, 218, 88, 220, 189, -1, -1, -1, 222, 3, + 223, -1, 177, 178, -1, -1, -1, 226, 3, 227, + 4, -1, -1, -1, 229, 13, 88, 3, 14, 3, + 3, 87, 3, 87, 3, 15, 230, -1, 232, -1, + 231, 88, 232, -1, -1, -1, 91, 233, 8, 234, + 92, 178, -1, -1, -1, 236, 3, 237, -1, -1, + 239, 240, 178, -1, -1, 8, 241, -1, -1, -1, + 215, 91, 242, 8, 243, 92, -1, -1, -1, 245, + 199, 246, -1, -1, -1, 244, 88, 247, 199, 248, + -1, -1, -1, 250, 3, 251, -1, -1, -1, 253, + 3, 254, -1, 4, -1, -1, -1, 257, 7, 258, + -1, -1, 260, 240, -1, 259, 88, 240, -1, -1, + 262, 240, -1, 261, 88, 240, -1, 4, -1, 4, + -1, 264, 89, 4, -1, 4, -1, 265, 88, 4, + -1, 267, -1, 266, 88, 267, -1, -1, -1, 268, + 215, 91, 8, 269, 92, 178, -1, 267, -1, 270, + 88, 267, -1, -1, -1, 272, 199, 273, 178, -1, + -1, 275, 240, 178, -1, 4, -1, 265, 88, 4, + -1, -1, -1, 278, 3, 279, 280, 178, -1, -1, + -1, -1, 94, 281, 6, 282, 95, -1, 267, -1, + 283, 88, 267, -1, 285, -1, 284, 285, -1, 280, + -1, 4, 280, -1, 4, 86, 4, 280, -1, -1, + -1, 287, 7, 288, -1, -1, 4, -1, 289, 88, + 4, -1, -1, 291, 292, -1, 293, -1, 293, 294, + -1, 3, -1, 3, 96, 3, -1, 3, -1, 3, + 96, 3, -1, -1, 296, 240, 178, -1, 4, -1, + 297, 88, 4, -1, 285, -1, 298, 285, -1, 300, + -1, 299, 88, 300, -1, 301, 302, 178, -1, 4, + 86, 4, 86, 4, -1, 4, -1, -1, -1, 4, + 87, 303, 3, 304, -1, 307, -1, -1, -1, 307, + 87, 305, 3, 306, -1, -1, -1, 97, 308, 20, + 309, 98, -1, 311, -1, 310, 88, 311, -1, -1, + -1, 312, 3, 313, 302, 5, -1, -1, -1, 315, + 7, 316, -1, 179, -1, 317, -1, 318, 88, 317, + -1, 179, -1, 319, 88, 179, -1, 179, -1, 320, + 88, 179, -1, -1, 18, 322, 319, -1, -1, 19, + 323, 320, -1, -1, 325, 321, -1, 179, -1, 326, + 88, 179, -1, -1, 18, 328, 326, -1, -1, 19, + 329, 320, -1, -1, 331, 327, -1, -1, 333, 321, + -1, -1, 335, 327, -1, -1, -1, 337, 3, 338, + -1, -1, -1, 340, 3, 3, 341, 4, -1, 4, + 178, -1, 4, -1, 343, 88, 4, -1, 4, 178, + -1, -1, 346, 240, 178, -1, -1, 348, 240, 178, + -1, 4, 178, -1, 4, -1, 4, -1, 4, -1, + 352, 88, 4, -1 +}; + +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const yytype_uint16 yyrline[] = +{ + 0, 244, 244, 244, 247, 248, 249, 269, 278, 278, + 278, 296, 296, 300, 307, 307, 308, 307, 318, 319, + 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, + 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, + 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, + 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, + 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, + 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, + 382, 386, 388, 390, 392, 394, 396, 398, 400, 402, + 404, 406, 408, 410, 412, 414, 416, 418, 420, 422, + 424, 426, 428, 430, 432, 434, 436, 438, 440, 442, + 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, + 464, 466, 468, 470, 472, 474, 476, 478, 480, 482, + 484, 486, 488, 490, 492, 494, 496, 498, 500, 502, + 504, 517, 519, 521, 523, 525, 527, 529, 531, 533, + 535, 537, 539, 541, 543, 545, 547, 549, 551, 553, + 555, 557, 559, 561, 563, 565, 567, 569, 571, 573, + 575, 577, 579, 581, 583, 585, 587, 589, 591, 593, + 595, 597, 599, 601, 603, 605, 607, 609, 611, 613, + 615, 617, 619, 621, 623, 625, 627, 629, 631, 633, + 635, 638, 639, 644, 651, 657, 658, 664, 668, 668, + 668, 675, 677, 681, 684, 689, 693, 700, 707, 707, + 710, 710, 715, 720, 720, 727, 730, 735, 735, 735, + 750, 753, 758, 758, 758, 763, 764, 771, 774, 779, + 779, 779, 794, 794, 794, 796, 801, 806, 812, 828, + 828, 828, 839, 839, 839, 839, 854, 855, 859, 862, + 878, 881, 886, 886, 889, 889, 894, 894, 894, 898, + 905, 905, 905, 911, 914, 911, 925, 928, 933, 933, + 933, 948, 948, 948, 952, 952, 967, 967, 978, 978, + 978, 993, 993, 993, 996, 996, 996, 1001, 1001, 1001, + 1005, 1005, 1005, 1009, 1014, 1014, 1014, 1019, 1019, 1022, + 1027, 1027, 1030, 1035, 1040, 1043, 1048, 1051, 1056, 1059, + 1064, 1064, 1064, 1082, 1085, 1090, 1090, 1090, 1112, 1112, + 1120, 1123, 1128, 1128, 1128, 1144, 1145, 1145, 1145, 1149, + 1152, 1157, 1160, 1165, 1170, 1177, 1188, 1188, 1188, 1193, + 1195, 1198, 1203, 1203, 1206, 1208, 1213, 1214, 1219, 1220, + 1225, 1225, 1240, 1243, 1248, 1251, 1256, 1259, 1264, 1292, + 1303, 1308, 1308, 1308, 1316, 1321, 1321, 1321, 1329, 1329, + 1329, 1336, 1339, 1344, 1344, 1344, 1355, 1355, 1355, 1358, + 1378, 1379, 1382, 1390, 1398, 1403, 1409, 1409, 1415, 1415, + 1424, 1424, 1429, 1437, 1445, 1445, 1451, 1451, 1460, 1460, + 1465, 1465, 1471, 1471, 1476, 1476, 1476, 1480, 1480, 1480, + 1487, 1501, 1504, 1509, 1535, 1535, 1543, 1543, 1558, 1569, + 1574, 1579, 1583 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "$end", "error", "$undefined", "T_NUM", "T_TOKEN", "T_QSTRING", + "T_COMMENT", "T_LINE", "T_URI", "T_URI_WILDCARD", "T_DISPLAY", "T_LANG", + "T_WORD", "T_WKDAY", "T_MONTH", "T_GMT", "T_SIP", "T_METHOD", + "T_AUTH_DIGEST", "T_AUTH_OTHER", "T_IPV6ADDR", "T_PARAMVAL", + "T_HDR_ACCEPT", "T_HDR_ACCEPT_ENCODING", "T_HDR_ACCEPT_LANGUAGE", + "T_HDR_ALERT_INFO", "T_HDR_ALLOW", "T_HDR_ALLOW_EVENTS", + "T_HDR_AUTHENTICATION_INFO", "T_HDR_AUTHORIZATION", "T_HDR_CALL_ID", + "T_HDR_CALL_INFO", "T_HDR_CONTACT", "T_HDR_CONTENT_DISP", + "T_HDR_CONTENT_ENCODING", "T_HDR_CONTENT_LANGUAGE", + "T_HDR_CONTENT_LENGTH", "T_HDR_CONTENT_TYPE", "T_HDR_CSEQ", "T_HDR_DATE", + "T_HDR_ERROR_INFO", "T_HDR_EVENT", "T_HDR_EXPIRES", "T_HDR_FROM", + "T_HDR_IN_REPLY_TO", "T_HDR_MAX_FORWARDS", "T_HDR_MIN_EXPIRES", + "T_HDR_MIME_VERSION", "T_HDR_ORGANIZATION", "T_HDR_P_ASSERTED_IDENTITY", + "T_HDR_P_PREFERRED_IDENTITY", "T_HDR_PRIORITY", "T_HDR_PRIVACY", + "T_HDR_PROXY_AUTHENTICATE", "T_HDR_PROXY_AUTHORIZATION", + "T_HDR_PROXY_REQUIRE", "T_HDR_RACK", "T_HDR_RECORD_ROUTE", + "T_HDR_SERVICE_ROUTE", "T_HDR_REFER_SUB", "T_HDR_REFER_TO", + "T_HDR_REFERRED_BY", "T_HDR_REPLACES", "T_HDR_REPLY_TO", "T_HDR_REQUIRE", + "T_HDR_REQUEST_DISPOSITION", "T_HDR_RETRY_AFTER", "T_HDR_ROUTE", + "T_HDR_RSEQ", "T_HDR_SERVER", "T_HDR_SIP_ETAG", "T_HDR_SIP_IF_MATCH", + "T_HDR_SUBJECT", "T_HDR_SUBSCRIPTION_STATE", "T_HDR_SUPPORTED", + "T_HDR_TIMESTAMP", "T_HDR_TO", "T_HDR_UNSUPPORTED", "T_HDR_USER_AGENT", + "T_HDR_VIA", "T_HDR_WARNING", "T_HDR_WWW_AUTHENTICATE", "T_HDR_UNKNOWN", + "T_CRLF", "T_ERROR", "T_NULL", "'/'", "':'", "','", "';'", "'='", "'<'", + "'>'", "'@'", "'('", "')'", "'.'", "'['", "']'", "$accept", + "sip_message", "@1", "sip_message2", "request", "request_line", "@2", + "@3", "sip_version", "@4", "response", "status_line", "@5", "@6", "@7", + "headers", "header", "hd_accept", "hd_accept_encoding", + "hd_accept_language", "hd_alert_info", "hd_allow", "hd_allow_events", + "hd_authentication_info", "hd_authorization", "hd_call_id", + "hd_call_info", "hd_contact", "hd_content_disp", "hd_content_encoding", + "hd_content_language", "hd_content_length", "hd_content_type", "hd_cseq", + "hd_date", "hd_error_info", "hd_event", "hd_expires", "hd_from", + "hd_in_reply_to", "hd_max_forwards", "hd_min_expires", "hd_mime_version", + "hd_organization", "hd_p_asserted_identity", "hd_p_preferred_identity", + "hd_priority", "hd_privacy", "hd_proxy_authenticate", + "hd_proxy_authorization", "hd_proxy_require", "hd_rack", + "hd_record_route", "hd_refer_sub", "hd_refer_to", "hd_referred_by", + "hd_replaces", "hd_reply_to", "hd_require", "hd_request_disposition", + "hd_retry_after", "hd_route", "hd_rseq", "hd_server", "hd_service_route", + "hd_sip_etag", "hd_sip_if_match", "hd_subject", "hd_subscription_state", + "hd_supported", "hd_timestamp", "hd_to", "hd_unsupported", + "hd_user_agent", "hd_via", "hd_warning", "hd_www_authenticate", + "hdr_accept", "media_range", "parameters", "parameter", "@8", "@9", + "parameter_val", "hdr_accept_encoding", "content_coding", "q_factor", + "hdr_accept_language", "@10", "@11", "language", "@12", "hdr_alert_info", + "alert_param", "@13", "@14", "hdr_allow", "hdr_call_id", "@15", "@16", + "call_id", "hdr_call_info", "info_param", "@17", "@18", "hdr_contact", + "@19", "@20", "contacts", "contact_param", "contact_addr", "@21", "@22", + "@23", "@24", "@25", "display_name", "hdr_content_disp", + "hdr_content_encoding", "hdr_content_language", "@26", "@27", + "hdr_content_length", "@28", "@29", "hdr_content_type", "hdr_cseq", + "@30", "@31", "hdr_date", "@32", "@33", "hdr_error_info", "error_param", + "@34", "@35", "hdr_expires", "@36", "@37", "hdr_from", "@38", + "from_addr", "@39", "@40", "@41", "hdr_in_reply_to", "@42", "@43", "@44", + "@45", "hdr_max_forwards", "@46", "@47", "hdr_min_expires", "@48", "@49", + "hdr_mime_version", "hdr_organization", "@50", "@51", + "hdr_p_asserted_identity", "@52", "hdr_p_preferred_identity", "@53", + "hdr_priority", "hdr_privacy", "hdr_proxy_require", "hdr_record_route", + "rec_route", "@54", "@55", "hdr_service_route", "hdr_replaces", "@56", + "@57", "hdr_reply_to", "@58", "hdr_require", "hdr_retry_after", "@59", + "@60", "comment", "@61", "@62", "hdr_route", "hdr_server", "server", + "hdr_subject", "@63", "@64", "hdr_supported", "hdr_timestamp", "@65", + "hdr_timestamp1", "timestamp", "delay", "hdr_to", "@66", + "hdr_unsupported", "hdr_user_agent", "hdr_via", "via_parm", + "sent_protocol", "host", "@67", "@68", "@69", "@70", "ipv6reference", + "@71", "@72", "hdr_warning", "warning", "@73", "@74", "hdr_unknown", + "@75", "@76", "ainfo", "hdr_authentication_info", "digest_response", + "auth_params", "credentials", "@77", "@78", "hdr_authorization", "@79", + "digest_challenge", "challenge", "@80", "@81", "hdr_proxy_authenticate", + "@82", "hdr_proxy_authorization", "@83", "hdr_www_authenticate", "@84", + "hdr_rseq", "@85", "@86", "hdr_rack", "@87", "@88", "hdr_event", + "hdr_allow_events", "hdr_subscription_state", "hdr_refer_to", "@89", + "hdr_referred_by", "@90", "hdr_refer_sub", "hdr_sip_etag", + "hdr_sip_if_match", "hdr_request_disposition", 0 +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to + token YYLEX-NUM. */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, + 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, + 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, + 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, + 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, + 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, + 335, 336, 337, 338, 339, 340, 47, 58, 44, 59, + 61, 60, 62, 64, 40, 41, 46, 91, 93 +}; +# endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint16 yyr1[] = +{ + 0, 99, 101, 100, 102, 102, 102, 103, 105, 106, + 104, 108, 107, 109, 111, 112, 113, 110, 114, 114, + 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, + 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, + 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, + 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, + 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, + 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, + 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, + 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, + 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, + 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, + 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, + 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, + 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, + 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, + 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, + 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, + 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, + 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, + 175, 176, 176, 176, 177, 178, 178, 179, 180, 181, + 179, 182, 182, 183, 183, 184, 184, 185, 187, 186, + 188, 186, 189, 190, 189, 191, 191, 193, 194, 192, + 195, 195, 197, 198, 196, 199, 199, 200, 200, 202, + 203, 201, 205, 206, 204, 204, 207, 207, 208, 210, + 211, 209, 212, 213, 214, 209, 215, 215, 215, 216, + 217, 217, 219, 218, 220, 218, 222, 223, 221, 224, + 226, 227, 225, 229, 230, 228, 231, 231, 233, 234, + 232, 236, 237, 235, 239, 238, 241, 240, 242, 243, + 240, 245, 246, 244, 247, 248, 244, 250, 251, 249, + 253, 254, 252, 255, 257, 258, 256, 260, 259, 259, + 262, 261, 261, 263, 264, 264, 265, 265, 266, 266, + 268, 269, 267, 270, 270, 272, 273, 271, 275, 274, + 276, 276, 278, 279, 277, 280, 281, 282, 280, 283, + 283, 284, 284, 285, 285, 285, 287, 288, 286, 289, + 289, 289, 291, 290, 292, 292, 293, 293, 294, 294, + 296, 295, 297, 297, 298, 298, 299, 299, 300, 301, + 302, 303, 304, 302, 302, 305, 306, 302, 308, 309, + 307, 310, 310, 312, 313, 311, 315, 316, 314, 317, + 318, 318, 319, 319, 320, 320, 322, 321, 323, 321, + 325, 324, 326, 326, 328, 327, 329, 327, 331, 330, + 333, 332, 335, 334, 337, 338, 336, 340, 341, 339, + 342, 343, 343, 344, 346, 345, 348, 347, 349, 350, + 351, 352, 352 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 0, 2, 1, 1, 2, 3, 0, 0, + 6, 0, 4, 3, 0, 0, 0, 7, 0, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 0, 2, 4, 3, 0, 3, 1, 0, 0, + 5, 1, 1, 1, 3, 1, 2, 2, 0, 2, + 0, 4, 1, 0, 3, 1, 3, 0, 0, 6, + 1, 3, 0, 0, 3, 1, 3, 1, 3, 0, + 0, 6, 0, 0, 3, 1, 1, 3, 2, 0, + 0, 3, 0, 0, 0, 7, 0, 1, 1, 2, + 1, 3, 0, 2, 0, 4, 0, 0, 3, 2, + 0, 0, 4, 0, 0, 13, 1, 3, 0, 0, + 6, 0, 0, 3, 0, 3, 0, 2, 0, 0, + 6, 0, 0, 3, 0, 0, 5, 0, 0, 3, + 0, 0, 3, 1, 0, 0, 3, 0, 2, 3, + 0, 2, 3, 1, 1, 3, 1, 3, 1, 3, + 0, 0, 7, 1, 3, 0, 0, 4, 0, 3, + 1, 3, 0, 0, 5, 0, 0, 0, 5, 1, + 3, 1, 2, 1, 2, 4, 0, 0, 3, 0, + 1, 3, 0, 2, 1, 2, 1, 3, 1, 3, + 0, 3, 1, 3, 1, 2, 1, 3, 3, 5, + 1, 0, 0, 5, 1, 0, 0, 5, 0, 0, + 5, 1, 3, 0, 0, 5, 0, 0, 3, 1, + 1, 3, 1, 3, 1, 3, 0, 3, 0, 3, + 0, 2, 1, 3, 0, 3, 0, 3, 0, 2, + 0, 2, 0, 2, 0, 0, 3, 0, 0, 5, + 2, 1, 3, 2, 0, 3, 0, 3, 2, 1, + 1, 1, 3 +}; + +/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state + STATE-NUM when YYTABLE doesn't specify something else to do. Zero + means the default is an error. */ +static const yytype_uint16 yydefact[] = +{ + 2, 0, 0, 1, 0, 11, 8, 3, 4, 18, + 14, 5, 18, 6, 0, 0, 0, 0, 0, 0, + 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 7, 19, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 15, 13, 12, 0, 141, 142, + 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, + 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, + 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, + 173, 174, 175, 176, 188, 177, 178, 179, 180, 181, + 182, 183, 184, 185, 186, 187, 189, 190, 191, 192, + 193, 194, 195, 196, 197, 198, 199, 200, 386, 0, + 0, 0, 205, 0, 215, 0, 213, 0, 0, 0, + 0, 227, 0, 225, 0, 230, 0, 0, 421, 0, + 0, 207, 389, 390, 0, 0, 0, 0, 0, 0, + 0, 0, 239, 0, 237, 0, 0, 0, 245, 246, + 205, 0, 256, 0, 205, 0, 0, 260, 0, 0, + 0, 0, 0, 0, 0, 0, 205, 0, 0, 0, + 0, 0, 0, 0, 0, 278, 0, 276, 0, 205, + 0, 0, 0, 0, 0, 0, 256, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 303, 0, 0, + 0, 0, 0, 0, 256, 0, 0, 256, 0, 313, + 0, 0, 314, 0, 0, 0, 0, 0, 0, 0, + 0, 316, 0, 0, 0, 0, 0, 0, 318, 256, + 0, 205, 0, 0, 0, 256, 0, 0, 256, 0, + 0, 0, 0, 0, 256, 0, 316, 0, 0, 0, + 431, 0, 0, 0, 0, 0, 339, 0, 0, 0, + 0, 0, 335, 336, 343, 0, 341, 0, 323, 0, + 0, 429, 0, 0, 430, 0, 0, 0, 0, 0, + 205, 0, 0, 350, 0, 0, 0, 0, 0, 0, + 256, 0, 362, 0, 0, 364, 0, 0, 0, 0, + 366, 0, 0, 0, 381, 0, 0, 0, 0, 0, + 0, 0, 0, 81, 0, 20, 0, 202, 82, 0, + 216, 21, 0, 83, 22, 220, 222, 219, 84, 0, + 23, 0, 85, 24, 0, 86, 25, 0, 87, 208, + 26, 0, 88, 27, 396, 398, 401, 89, 28, 235, + 233, 90, 0, 29, 0, 91, 30, 243, 252, 248, + 250, 258, 257, 0, 92, 259, 31, 93, 32, 0, + 94, 33, 264, 263, 95, 34, 267, 96, 269, 35, + 97, 36, 271, 98, 37, 0, 99, 0, 39, 0, + 100, 420, 38, 101, 40, 282, 102, 41, 286, 0, + 205, 103, 42, 294, 292, 104, 43, 298, 105, 44, + 301, 106, 45, 107, 46, 305, 108, 47, 256, 308, + 109, 48, 256, 311, 110, 49, 111, 50, 0, 112, + 51, 404, 406, 409, 113, 52, 411, 114, 53, 0, + 115, 54, 0, 116, 55, 320, 0, 117, 428, 56, + 118, 57, 205, 119, 58, 205, 120, 59, 326, 121, + 60, 205, 122, 0, 61, 123, 62, 0, 124, 63, + 333, 125, 64, 320, 126, 65, 415, 127, 0, 344, + 0, 66, 342, 128, 67, 320, 129, 68, 130, 69, + 131, 70, 347, 132, 423, 71, 133, 72, 0, 134, + 73, 356, 353, 354, 135, 74, 205, 136, 75, 0, + 137, 76, 365, 138, 0, 77, 0, 370, 378, 205, + 374, 139, 78, 383, 384, 140, 79, 413, 16, 10, + 80, 387, 204, 205, 0, 217, 214, 0, 0, 228, + 226, 231, 422, 0, 391, 0, 0, 0, 234, 240, + 238, 244, 247, 251, 253, 261, 0, 268, 0, 0, + 279, 277, 283, 287, 288, 285, 0, 293, 299, 302, + 306, 309, 312, 315, 0, 0, 317, 418, 319, 0, + 425, 427, 205, 329, 317, 432, 335, 340, 416, 335, + 337, 324, 348, 351, 0, 358, 355, 361, 363, 0, + 367, 371, 0, 368, 375, 382, 0, 0, 388, 203, + 206, 221, 224, 0, 212, 211, 209, 392, 397, 394, + 399, 236, 0, 0, 265, 272, 0, 0, 0, 295, + 402, 405, 407, 0, 321, 327, 205, 345, 0, 357, + 0, 0, 0, 379, 0, 0, 17, 205, 210, 0, + 0, 205, 254, 0, 205, 289, 296, 0, 419, 0, + 334, 338, 359, 369, 372, 0, 376, 385, 229, 393, + 395, 241, 0, 0, 280, 0, 403, 205, 373, 380, + 377, 255, 0, 290, 322, 0, 0, 0, 0, 274, + 275 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int16 yydefgoto[] = +{ + -1, 1, 2, 7, 8, 9, 15, 147, 10, 14, + 11, 12, 17, 409, 707, 16, 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, 129, 130, 131, 132, 133, 134, 135, 136, + 137, 138, 139, 140, 141, 142, 143, 211, 212, 417, + 232, 643, 748, 716, 215, 216, 420, 218, 219, 637, + 427, 638, 222, 223, 429, 713, 226, 239, 240, 648, + 450, 243, 244, 452, 722, 246, 247, 651, 248, 249, + 250, 251, 653, 252, 723, 772, 499, 255, 258, 260, + 261, 656, 263, 264, 657, 267, 269, 270, 658, 272, + 273, 790, 276, 277, 487, 727, 282, 283, 662, 285, + 286, 500, 663, 728, 775, 288, 289, 667, 666, 756, + 291, 292, 668, 294, 295, 669, 298, 300, 301, 670, + 303, 304, 306, 307, 310, 313, 322, 327, 328, 329, + 759, 369, 340, 341, 682, 343, 344, 348, 353, 354, + 686, 364, 580, 738, 357, 365, 366, 377, 378, 692, + 384, 386, 387, 602, 603, 696, 389, 390, 393, 396, + 399, 400, 401, 619, 742, 778, 744, 780, 620, 702, + 765, 403, 404, 405, 706, 411, 412, 708, 233, 234, + 718, 720, 446, 645, 646, 236, 237, 731, 533, 674, + 675, 315, 316, 318, 319, 407, 408, 359, 360, 688, + 324, 325, 733, 280, 229, 381, 334, 335, 337, 338, + 332, 372, 375, 351 +}; + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +#define YYPACT_NINF -413 +static const yytype_int16 yypact[] = +{ + -413, 90, 99, -413, -37, -413, -413, -413, -413, -413, + -413, -413, -413, -413, 18, 119, 506, 133, 568, 152, + -413, 77, 107, 139, 145, 168, 177, 194, 198, 199, + 200, 201, 202, 203, 204, 206, 208, 209, 210, 211, + 216, 217, 221, 222, 223, 225, 228, 229, 230, 231, + 232, 233, 234, 239, 240, 244, 254, 255, 256, 259, + 260, 261, 262, 263, 266, 270, 273, 274, 277, 278, + 280, 281, 283, 284, 285, 286, 287, 288, 289, 291, + 294, 295, -413, -413, 58, 147, 120, 20, 205, 207, + 212, 67, 81, 34, 17, 213, 214, 123, 257, 219, + 258, 64, 37, 220, 264, 23, 95, 267, 268, 226, + 88, 29, 42, 227, 235, 73, 100, 236, 271, 31, + 237, 48, 56, 111, 62, 241, 242, 272, 70, 275, + 11, 78, 243, 247, 128, 248, 83, 276, 68, 252, + 16, 253, 279, 125, -413, -413, -413, 87, -413, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, 136, + 215, 66, -413, 224, 245, 69, -413, 246, 72, 167, + 269, -413, 75, -413, 301, -413, 79, 302, -413, 82, + 303, 238, -413, -413, 85, 304, 305, 164, 306, 307, + 282, 308, -413, 89, -413, 309, 310, 290, 218, -413, + -413, 292, 140, 311, -413, 312, 314, -413, 91, 315, + 92, 167, 316, 317, 319, 318, -413, 320, 321, 322, + 327, 323, 324, 300, 325, -413, 93, -413, 326, -413, + 328, 329, 330, 332, 331, 333, 132, 334, 101, 282, + 335, 337, 336, 338, 339, 341, 340, -413, 342, 343, + 344, 347, 345, 102, 132, 346, 103, 132, 348, -413, + 350, 351, -413, 49, 352, 353, 265, 354, 355, 164, + 356, -413, 104, 357, 358, 359, 360, 105, -413, 140, + 361, -413, 362, 363, 365, 132, 366, 367, 132, 368, + 369, 282, 370, 371, 132, 372, 373, 374, 375, 377, + -413, 112, 378, 380, 399, 381, -413, 113, 382, 383, + 407, 384, -40, -413, -413, 15, -413, 385, -413, 114, + 386, -413, 387, 388, -413, 389, 390, 391, 408, 392, + -413, 393, 394, -413, 115, 395, 396, 421, 397, 398, + 132, 400, -413, 116, 401, -413, 19, 402, 403, 122, + -413, 9, 404, 124, -413, 427, 405, 409, 265, 435, + 410, 411, 450, -413, 478, -413, 486, 251, -413, 487, + -413, -413, 491, -413, -413, -413, 413, -413, -413, 488, + -413, 412, -413, -413, 494, -413, -413, 495, -413, -413, + -413, 487, -413, -413, -413, -413, -413, -413, -413, 414, + -413, -413, 492, -413, 415, -413, -413, -413, 497, 251, + -413, -413, -413, 417, -413, 251, -413, -413, -413, 491, + -413, -413, -413, -413, -413, -413, -413, -413, 251, -413, + -413, -413, -413, -413, -413, 422, -413, 501, -413, 420, + -413, 251, -413, -413, -413, -413, -413, -413, -413, 423, + -413, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, -413, -413, -413, -413, -413, 132, -413, + -413, -413, 132, -413, -413, -413, -413, -413, 508, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, 509, + -413, -413, 512, -413, -413, -413, 425, -413, 251, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, 513, -413, -413, -413, 514, -413, -413, + -413, -413, -413, -413, -413, -413, -413, -413, 515, -413, + 349, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, -413, 251, -413, -413, -413, 516, -413, + -413, 196, -413, 518, -413, -413, -413, -413, -413, 519, + -413, -413, -413, -413, 520, -413, 521, 439, -413, -413, + 565, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, -413, 487, -413, -413, 167, 245, -413, + -413, -413, -413, 39, -413, 487, 487, 510, -413, -413, + -413, -413, -413, -413, -413, -413, 167, -413, 649, 651, + -413, -413, -413, -413, -413, 251, 282, -413, -413, -413, + -413, -413, -413, -413, 487, 487, -413, -413, -413, 647, + 251, 251, -413, 251, 573, -413, 563, -413, -413, 563, + -413, -413, -413, -413, 655, 564, -413, 251, -413, 575, + -413, -413, 639, 251, -413, -413, 9, 579, -413, 251, + -413, -413, -413, 571, -413, -413, -413, -413, 576, -413, + 577, -413, 574, 659, -413, -413, 654, 578, 661, -413, + -413, 583, 577, 668, -413, 251, -413, -413, 580, -413, + 670, 672, 671, -413, 674, 673, -413, -413, -413, 487, + 487, -413, -413, 676, -413, -413, -413, 487, -413, 588, + 251, -413, -413, -413, -413, 584, -413, -413, 251, -413, + -413, 251, 589, 680, 251, 592, -413, -413, -413, -413, + -413, -413, 598, -413, 251, 683, 600, 685, 299, -413, + -413 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int16 yypgoto[] = +{ + -413, -413, -413, -413, -413, -413, -413, -413, 176, -413, + -413, -413, -413, -413, -413, 677, -413, -413, -413, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -91, -250, + -412, -413, -413, -413, -413, -90, -314, -413, -413, -413, + -260, -413, -413, -80, -413, -413, -413, -413, -413, -413, + -286, -413, -96, -413, -413, -413, -413, -413, -413, -99, + -413, -413, -413, -413, -413, -413, -212, -413, -413, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, -123, -413, -413, -413, -413, -413, -413, + -413, -293, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, -413, -413, -413, 566, -413, -126, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -353, -413, -413, -413, -413, -130, -413, -413, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -253, -413, -16, -413, -413, -413, -413, -413, -413, + -413, -413, 71, -413, -413, -413, -413, -413, 293, -413, + -413, 21, 376, -413, -413, -413, -413, -413, 296, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, -413, -413, -413, -413, -413, -413, -413, + -413, -413, -413, -413 +}; + +/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule which + number is the opposite. If zero, do what YYDEFACT says. + If YYTABLE_NINF, syntax error. */ +#define YYTABLE_NINF -427 +static const yytype_int16 yytable[] = +{ + 459, 473, 356, 504, 465, 368, 257, 635, 266, 579, + 395, 519, 361, 617, 523, 362, 478, 394, 245, 362, + 362, 220, -252, 362, 284, -249, -242, -252, -284, 491, + 302, -284, 326, -284, -307, 241, -320, -307, 274, -307, + 463, -320, 552, 305, 714, 555, 578, -310, 13, 333, + -310, 561, -310, -424, 363, 558, -424, 336, -424, 209, + 715, -426, 210, 342, -426, 271, -426, -328, 235, 388, + -328, 355, -328, -360, 314, -320, -360, -273, -360, 367, + -320, 548, 238, -320, 382, -400, -400, 383, -320, 299, + 3, -408, -408, -232, -335, -304, 287, 606, 581, -335, + 4, 317, 611, 5, 19, 363, 618, -291, -252, 363, + 363, 221, 339, 363, -284, 5, 6, 546, -410, -410, + -307, 217, -320, -325, 259, 242, 406, 20, 275, 376, + 594, -218, 527, -310, -262, -346, 144, 461, 528, -424, + 498, -201, 462, -412, -412, 461, -201, -426, 213, 415, + 462, 214, 421, -328, 416, 424, 146, 422, 430, -360, + 425, -320, 433, 431, 148, 436, -349, 434, 440, -320, + 437, -349, 453, 441, 468, 471, 488, 454, 426, 469, + 472, 489, 444, 445, 502, 517, 521, 538, 544, 503, + 518, 522, 539, 545, 149, 566, 572, 584, 597, 608, + 567, 573, 585, 598, 609, 615, 224, 622, 227, 225, + 616, 228, 623, 230, 253, 256, 231, 254, 214, 413, + 265, 278, 710, 210, 279, 671, 150, 296, 308, 672, + 297, 309, 151, 717, 719, 582, 311, 320, 330, 312, + 321, 331, 345, 349, 370, 346, 350, 371, 373, 379, + 665, 374, 380, 391, 397, 152, 392, 398, 262, 268, + -266, -270, 730, 719, 153, 281, 612, -281, 290, 293, + -297, -300, 323, 352, -417, -332, 358, 385, -414, -352, + 402, 154, -383, 531, 532, 155, 156, 157, 158, 159, + 160, 161, 694, 162, 449, 163, 164, 165, 166, 457, + 460, 414, 680, 167, 168, 681, 458, 418, 169, 170, + 171, 683, 172, 485, 789, 173, 174, 175, 176, 177, + 178, 179, 476, 410, 712, 633, 180, 181, 439, 423, + 482, 182, 636, 736, 419, 495, 737, 769, 770, 507, + 634, 183, 184, 185, 510, 776, 186, 187, 188, 189, + 190, 640, 428, 191, 515, 690, 697, 192, 650, 652, + 193, 194, 542, 700, 195, 196, 661, 197, 198, 703, + 199, 200, 201, 202, 203, 204, 205, 711, 206, 655, + 729, 207, 208, 709, 432, 435, 438, 442, 443, 447, + 448, 451, 455, 456, 464, 466, 724, 467, 470, 474, + 475, 477, 570, 479, 480, 481, 483, 484, 486, 490, + 576, 492, 493, 494, 496, 592, 497, 501, 505, 678, + 506, 508, 509, 511, 601, 512, 513, 514, 516, 520, + 624, 524, 735, 525, 526, 529, 530, 534, 535, 537, + 540, 541, 628, 543, 547, 549, 550, 687, 551, 553, + 554, 556, 557, 559, 560, 562, -330, 631, 564, 691, + 565, 568, 563, 569, 571, 574, 575, 577, 583, 586, + 587, 588, 589, 590, 591, 593, 595, 596, 599, 600, + 604, 605, 632, 607, 610, 613, 760, 621, 625, 614, + 210, 231, 626, 629, 630, 214, 639, 768, 641, 642, + 649, 771, -223, 221, 774, -249, 242, 647, 654, 660, + 659, 275, 673, 676, 664, 677, 679, 684, 685, 689, + 693, 695, 721, 698, 699, 398, 701, 784, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, + 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, + 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, + 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + 81, 145, 704, 725, 726, 734, -331, 363, 739, 743, + 740, 741, 746, 747, 749, 750, 751, 752, 753, 755, + 754, 757, 758, 762, 764, 761, 763, 766, 767, 773, + 777, 781, 779, 782, 783, 785, 786, 787, 788, 18, + 745, 347, 0, 0, 705, 536, 732, 0, 0, 0, + 0, 0, 0, 0, 627, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 644 +}; + +static const yytype_int16 yycheck[] = +{ + 250, 261, 128, 289, 254, 131, 96, 419, 99, 362, + 140, 304, 1, 4, 307, 4, 266, 1, 1, 4, + 4, 1, 5, 4, 1, 8, 9, 10, 5, 279, + 1, 8, 1, 10, 5, 1, 5, 8, 1, 10, + 252, 10, 335, 1, 5, 338, 86, 5, 85, 1, + 8, 344, 10, 5, 94, 341, 8, 1, 10, 1, + 21, 5, 4, 1, 8, 1, 10, 5, 1, 1, + 8, 1, 10, 5, 1, 5, 8, 13, 10, 1, + 10, 331, 1, 5, 1, 18, 19, 4, 10, 1, + 0, 18, 19, 12, 83, 7, 1, 390, 83, 83, + 1, 1, 83, 16, 86, 94, 97, 12, 91, 94, + 94, 91, 1, 94, 91, 16, 17, 329, 18, 19, + 91, 1, 91, 12, 1, 91, 1, 8, 91, 1, + 380, 11, 83, 91, 11, 7, 3, 5, 89, 91, + 8, 83, 10, 18, 19, 5, 88, 91, 1, 83, + 10, 4, 83, 91, 88, 83, 4, 88, 83, 91, + 88, 91, 83, 88, 87, 83, 83, 88, 83, 91, + 88, 88, 83, 88, 83, 83, 83, 88, 11, 88, + 88, 88, 18, 19, 83, 83, 83, 83, 83, 88, + 88, 88, 88, 88, 87, 83, 83, 83, 83, 83, + 88, 88, 88, 88, 88, 83, 1, 83, 1, 4, + 88, 4, 88, 1, 1, 1, 4, 4, 4, 83, + 1, 1, 634, 4, 4, 518, 87, 1, 1, 522, + 4, 4, 87, 645, 646, 365, 1, 1, 1, 4, + 4, 4, 1, 1, 1, 4, 4, 4, 1, 1, + 500, 4, 4, 1, 1, 87, 4, 4, 1, 1, + 3, 3, 674, 675, 87, 1, 396, 3, 1, 1, + 3, 3, 1, 1, 3, 3, 1, 1, 3, 3, + 1, 87, 3, 18, 19, 87, 87, 87, 87, 87, + 87, 87, 96, 87, 12, 87, 87, 87, 87, 9, + 8, 86, 552, 87, 87, 555, 88, 83, 87, 87, + 87, 561, 87, 13, 15, 87, 87, 87, 87, 87, + 87, 87, 3, 147, 638, 416, 87, 87, 90, 83, + 3, 87, 422, 686, 89, 3, 689, 749, 750, 3, + 89, 87, 87, 87, 3, 757, 87, 87, 87, 87, + 87, 431, 83, 87, 7, 6, 606, 87, 454, 458, + 87, 87, 3, 616, 87, 87, 489, 87, 87, 619, + 87, 87, 87, 87, 87, 87, 87, 637, 87, 469, + 666, 87, 87, 633, 83, 83, 83, 83, 83, 83, + 83, 83, 83, 83, 83, 83, 656, 83, 83, 83, + 83, 83, 3, 83, 83, 83, 83, 83, 83, 83, + 3, 83, 83, 83, 83, 7, 83, 83, 83, 545, + 83, 83, 83, 83, 3, 83, 83, 83, 83, 83, + 3, 83, 682, 83, 83, 83, 83, 83, 83, 83, + 83, 83, 7, 83, 83, 83, 83, 573, 83, 83, + 83, 83, 83, 83, 83, 83, 83, 7, 83, 585, + 83, 83, 88, 83, 83, 83, 83, 83, 83, 83, + 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, + 83, 83, 4, 83, 83, 83, 736, 83, 83, 86, + 4, 4, 83, 83, 83, 4, 8, 747, 4, 4, + 8, 751, 89, 91, 754, 8, 91, 93, 91, 8, + 88, 91, 4, 4, 91, 3, 91, 4, 4, 4, + 4, 3, 12, 4, 4, 4, 87, 777, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, + 82, 83, 87, 4, 3, 8, 83, 94, 3, 20, + 96, 86, 83, 92, 88, 88, 92, 8, 14, 8, + 92, 88, 4, 3, 3, 95, 4, 3, 5, 3, + 92, 92, 98, 3, 92, 87, 3, 87, 3, 12, + 706, 125, -1, -1, 623, 319, 675, -1, -1, -1, + -1, -1, -1, -1, 408, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 441 +}; + +/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint16 yystos[] = +{ + 0, 100, 101, 0, 1, 16, 17, 102, 103, 104, + 107, 109, 110, 85, 108, 105, 114, 111, 114, 86, + 8, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, + 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + 81, 82, 83, 115, 116, 117, 118, 119, 120, 121, + 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, + 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, + 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, + 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, + 172, 173, 174, 175, 3, 83, 4, 106, 87, 87, + 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, + 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, + 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, + 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, + 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, + 87, 87, 87, 87, 87, 87, 87, 87, 87, 1, + 4, 176, 177, 1, 4, 183, 184, 1, 186, 187, + 1, 91, 191, 192, 1, 4, 195, 1, 4, 343, + 1, 4, 179, 317, 318, 1, 324, 325, 1, 196, + 197, 1, 91, 200, 201, 1, 204, 205, 207, 208, + 209, 210, 212, 1, 4, 216, 1, 184, 217, 1, + 218, 219, 1, 221, 222, 1, 177, 224, 1, 225, + 226, 1, 228, 229, 1, 91, 231, 232, 1, 4, + 342, 1, 235, 236, 1, 238, 239, 1, 244, 245, + 1, 249, 250, 1, 252, 253, 1, 4, 255, 1, + 256, 257, 1, 259, 260, 1, 261, 262, 1, 4, + 263, 1, 4, 264, 1, 330, 331, 1, 332, 333, + 1, 4, 265, 1, 339, 340, 1, 266, 267, 268, + 1, 4, 349, 1, 345, 346, 1, 347, 348, 1, + 271, 272, 1, 274, 275, 1, 4, 265, 276, 1, + 4, 352, 1, 277, 278, 1, 267, 283, 1, 336, + 337, 1, 4, 94, 280, 284, 285, 1, 267, 270, + 1, 4, 350, 1, 4, 351, 1, 286, 287, 1, + 4, 344, 1, 4, 289, 1, 290, 291, 1, 295, + 296, 1, 4, 297, 1, 285, 298, 1, 4, 299, + 300, 301, 1, 310, 311, 312, 1, 334, 335, 112, + 107, 314, 315, 83, 86, 83, 88, 178, 83, 89, + 185, 83, 88, 83, 83, 88, 11, 189, 83, 193, + 83, 88, 83, 83, 88, 83, 83, 88, 83, 90, + 83, 88, 83, 83, 18, 19, 321, 83, 83, 12, + 199, 83, 202, 83, 88, 83, 83, 9, 88, 178, + 8, 5, 10, 215, 83, 178, 83, 83, 83, 88, + 83, 83, 88, 189, 83, 83, 3, 83, 178, 83, + 83, 83, 3, 83, 83, 13, 83, 233, 83, 88, + 83, 178, 83, 83, 83, 3, 83, 83, 8, 215, + 240, 83, 83, 88, 199, 83, 83, 3, 83, 83, + 3, 83, 83, 83, 83, 7, 83, 83, 88, 240, + 83, 83, 88, 240, 83, 83, 83, 83, 89, 83, + 83, 18, 19, 327, 83, 83, 321, 83, 83, 88, + 83, 83, 3, 83, 83, 88, 215, 83, 178, 83, + 83, 83, 240, 83, 83, 240, 83, 83, 199, 83, + 83, 240, 83, 88, 83, 83, 83, 88, 83, 83, + 3, 83, 83, 88, 83, 83, 3, 83, 86, 280, + 281, 83, 285, 83, 83, 88, 83, 83, 83, 83, + 83, 83, 7, 83, 178, 83, 83, 83, 88, 83, + 83, 3, 292, 293, 83, 83, 240, 83, 83, 88, + 83, 83, 285, 83, 86, 83, 88, 4, 97, 302, + 307, 83, 83, 88, 3, 83, 83, 327, 7, 83, + 83, 7, 4, 177, 89, 179, 184, 188, 190, 8, + 192, 4, 4, 180, 317, 322, 323, 93, 198, 8, + 201, 206, 208, 211, 91, 184, 220, 223, 227, 88, + 8, 232, 237, 241, 91, 178, 247, 246, 251, 254, + 258, 240, 240, 4, 328, 329, 4, 3, 267, 91, + 178, 178, 273, 178, 4, 4, 279, 267, 338, 4, + 6, 267, 288, 4, 96, 3, 294, 178, 4, 4, + 300, 87, 308, 178, 87, 311, 313, 113, 316, 178, + 179, 189, 185, 194, 5, 21, 182, 179, 319, 179, + 320, 12, 203, 213, 189, 4, 3, 234, 242, 199, + 179, 326, 320, 341, 8, 178, 280, 280, 282, 3, + 96, 86, 303, 20, 305, 302, 83, 92, 181, 88, + 88, 92, 8, 14, 92, 8, 248, 88, 4, 269, + 178, 95, 3, 4, 3, 309, 3, 5, 178, 179, + 179, 178, 214, 3, 178, 243, 179, 92, 304, 98, + 306, 92, 3, 92, 178, 87, 3, 87, 3, 15, + 230 +}; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. */ + +#define YYFAIL goto yyerrlab + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + yytoken = YYTRANSLATE (yychar); \ + YYPOPSTACK (1); \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (YYID (0)) + + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (YYID (N)) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (YYID (0)) +#endif + + +/* YY_LOCATION_PRINT -- Print the location on the stream. + This macro was not mandated originally: define only if we know + we won't break user code: when these are the locations we know. */ + +#ifndef YY_LOCATION_PRINT +# if YYLTYPE_IS_TRIVIAL +# define YY_LOCATION_PRINT(File, Loc) \ + fprintf (File, "%d.%d-%d.%d", \ + (Loc).first_line, (Loc).first_column, \ + (Loc).last_line, (Loc).last_column) +# else +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#ifdef YYLEX_PARAM +# define YYLEX yylex (YYLEX_PARAM) +#else +# define YYLEX yylex () +#endif + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (YYID (0)) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (YYID (0)) + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep) +#else +static void +yy_symbol_value_print (yyoutput, yytype, yyvaluep) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; +#endif +{ + if (!yyvaluep) + return; +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# else + YYUSE (yyoutput); +# endif + switch (yytype) + { + default: + break; + } +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep) +#else +static void +yy_symbol_print (yyoutput, yytype, yyvaluep) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; +#endif +{ + if (yytype < YYNTOKENS) + YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); + else + YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + + yy_symbol_value_print (yyoutput, yytype, yyvaluep); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_stack_print (yytype_int16 *bottom, yytype_int16 *top) +#else +static void +yy_stack_print (bottom, top) + yytype_int16 *bottom; + yytype_int16 *top; +#endif +{ + YYFPRINTF (stderr, "Stack now"); + for (; bottom <= top; ++bottom) + YYFPRINTF (stderr, " %d", *bottom); + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (YYID (0)) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_reduce_print (YYSTYPE *yyvsp, int yyrule) +#else +static void +yy_reduce_print (yyvsp, yyrule) + YYSTYPE *yyvsp; + int yyrule; +#endif +{ + int yynrhs = yyr2[yyrule]; + int yyi; + unsigned long int yylno = yyrline[yyrule]; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + fprintf (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi], + &(yyvsp[(yyi + 1) - (yynrhs)]) + ); + fprintf (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyvsp, Rule); \ +} while (YYID (0)) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static YYSIZE_T +yystrlen (const char *yystr) +#else +static YYSIZE_T +yystrlen (yystr) + const char *yystr; +#endif +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static char * +yystpcpy (char *yydest, const char *yysrc) +#else +static char * +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +#endif +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYSIZE_T yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into YYRESULT an error message about the unexpected token + YYCHAR while in state YYSTATE. Return the number of bytes copied, + including the terminating null byte. If YYRESULT is null, do not + copy anything; just return the number of bytes that would be + copied. As a special case, return 0 if an ordinary "syntax error" + message will do. Return YYSIZE_MAXIMUM if overflow occurs during + size calculation. */ +static YYSIZE_T +yysyntax_error (char *yyresult, int yystate, int yychar) +{ + int yyn = yypact[yystate]; + + if (! (YYPACT_NINF < yyn && yyn <= YYLAST)) + return 0; + else + { + int yytype = YYTRANSLATE (yychar); + YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]); + YYSIZE_T yysize = yysize0; + YYSIZE_T yysize1; + int yysize_overflow = 0; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + int yyx; + +# if 0 + /* This is so xgettext sees the translatable formats that are + constructed on the fly. */ + YY_("syntax error, unexpected %s"); + YY_("syntax error, unexpected %s, expecting %s"); + YY_("syntax error, unexpected %s, expecting %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"); +# endif + char *yyfmt; + char const *yyf; + static char const yyunexpected[] = "syntax error, unexpected %s"; + static char const yyexpecting[] = ", expecting %s"; + static char const yyor[] = " or %s"; + char yyformat[sizeof yyunexpected + + sizeof yyexpecting - 1 + + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2) + * (sizeof yyor - 1))]; + char const *yyprefix = yyexpecting; + + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yycount = 1; + + yyarg[0] = yytname[yytype]; + yyfmt = yystpcpy (yyformat, yyunexpected); + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + yyformat[sizeof yyunexpected - 1] = '\0'; + break; + } + yyarg[yycount++] = yytname[yyx]; + yysize1 = yysize + yytnamerr (0, yytname[yyx]); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + yyfmt = yystpcpy (yyfmt, yyprefix); + yyprefix = yyor; + } + + yyf = YY_(yyformat); + yysize1 = yysize + yystrlen (yyf); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + + if (yysize_overflow) + return YYSIZE_MAXIMUM; + + if (yyresult) + { + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + char *yyp = yyresult; + int yyi = 0; + while ((*yyp = *yyf) != '\0') + { + if (*yyp == '%' && yyf[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyf += 2; + } + else + { + yyp++; + yyf++; + } + } + } + return yysize; + } +} +#endif /* YYERROR_VERBOSE */ + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep) +#else +static void +yydestruct (yymsg, yytype, yyvaluep) + const char *yymsg; + int yytype; + YYSTYPE *yyvaluep; +#endif +{ + YYUSE (yyvaluep); + + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + case 4: /* "T_TOKEN" */ +#line 164 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2025 "parser.cxx" + break; + case 5: /* "T_QSTRING" */ +#line 165 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2030 "parser.cxx" + break; + case 6: /* "T_COMMENT" */ +#line 166 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2035 "parser.cxx" + break; + case 7: /* "T_LINE" */ +#line 167 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2040 "parser.cxx" + break; + case 8: /* "T_URI" */ +#line 168 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2045 "parser.cxx" + break; + case 10: /* "T_DISPLAY" */ +#line 169 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2050 "parser.cxx" + break; + case 11: /* "T_LANG" */ +#line 170 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2055 "parser.cxx" + break; + case 12: /* "T_WORD" */ +#line 171 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2060 "parser.cxx" + break; + case 17: /* "T_METHOD" */ +#line 172 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2065 "parser.cxx" + break; + case 19: /* "T_AUTH_OTHER" */ +#line 173 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2070 "parser.cxx" + break; + case 20: /* "T_IPV6ADDR" */ +#line 174 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2075 "parser.cxx" + break; + case 21: /* "T_PARAMVAL" */ +#line 175 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2080 "parser.cxx" + break; + case 82: /* "T_HDR_UNKNOWN" */ +#line 176 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2085 "parser.cxx" + break; + case 107: /* "sip_version" */ +#line 239 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2090 "parser.cxx" + break; + case 177: /* "media_range" */ +#line 232 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_media)); delete (yyvaluep->yyt_media); }; +#line 2095 "parser.cxx" + break; + case 178: /* "parameters" */ +#line 235 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_params)); delete (yyvaluep->yyt_params); }; +#line 2100 "parser.cxx" + break; + case 179: /* "parameter" */ +#line 233 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_param)); delete (yyvaluep->yyt_param); }; +#line 2105 "parser.cxx" + break; + case 182: /* "parameter_val" */ +#line 234 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2110 "parser.cxx" + break; + case 184: /* "content_coding" */ +#line 220 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_coding)); delete (yyvaluep->yyt_coding); }; +#line 2115 "parser.cxx" + break; + case 189: /* "language" */ +#line 231 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_language)); delete (yyvaluep->yyt_language); }; +#line 2120 "parser.cxx" + break; + case 192: /* "alert_param" */ +#line 212 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_alert_param)); delete (yyvaluep->yyt_alert_param); }; +#line 2125 "parser.cxx" + break; + case 199: /* "call_id" */ +#line 214 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2130 "parser.cxx" + break; + case 201: /* "info_param" */ +#line 230 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_info_param)); delete (yyvaluep->yyt_info_param); }; +#line 2135 "parser.cxx" + break; + case 207: /* "contacts" */ +#line 219 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_contacts)); delete (yyvaluep->yyt_contacts); }; +#line 2140 "parser.cxx" + break; + case 208: /* "contact_param" */ +#line 218 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_contact)); delete (yyvaluep->yyt_contact); }; +#line 2145 "parser.cxx" + break; + case 209: /* "contact_addr" */ +#line 217 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_contact)); delete (yyvaluep->yyt_contact); }; +#line 2150 "parser.cxx" + break; + case 215: /* "display_name" */ +#line 224 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2155 "parser.cxx" + break; + case 232: /* "error_param" */ +#line 225 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_error_param)); delete (yyvaluep->yyt_error_param); }; +#line 2160 "parser.cxx" + break; + case 240: /* "from_addr" */ +#line 226 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_from_addr)); delete (yyvaluep->yyt_from_addr); }; +#line 2165 "parser.cxx" + break; + case 267: /* "rec_route" */ +#line 236 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_route)); delete (yyvaluep->yyt_route); }; +#line 2170 "parser.cxx" + break; + case 280: /* "comment" */ +#line 216 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2175 "parser.cxx" + break; + case 285: /* "server" */ +#line 238 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_server)); delete (yyvaluep->yyt_server); }; +#line 2180 "parser.cxx" + break; + case 300: /* "via_parm" */ +#line 240 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_via)); delete (yyvaluep->yyt_via); }; +#line 2185 "parser.cxx" + break; + case 301: /* "sent_protocol" */ +#line 237 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_via)); delete (yyvaluep->yyt_via); }; +#line 2190 "parser.cxx" + break; + case 302: /* "host" */ +#line 228 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_via)); delete (yyvaluep->yyt_via); }; +#line 2195 "parser.cxx" + break; + case 307: /* "ipv6reference" */ +#line 229 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2200 "parser.cxx" + break; + case 311: /* "warning" */ +#line 241 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_warning)); delete (yyvaluep->yyt_warning); }; +#line 2205 "parser.cxx" + break; + case 314: /* "hdr_unknown" */ +#line 227 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_str)); delete (yyvaluep->yyt_str); }; +#line 2210 "parser.cxx" + break; + case 319: /* "digest_response" */ +#line 223 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_dig_resp)); delete (yyvaluep->yyt_dig_resp); }; +#line 2215 "parser.cxx" + break; + case 320: /* "auth_params" */ +#line 213 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_params)); delete (yyvaluep->yyt_params); }; +#line 2220 "parser.cxx" + break; + case 321: /* "credentials" */ +#line 221 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_credentials)); delete (yyvaluep->yyt_credentials); }; +#line 2225 "parser.cxx" + break; + case 326: /* "digest_challenge" */ +#line 222 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_dig_chlg)); delete (yyvaluep->yyt_dig_chlg); }; +#line 2230 "parser.cxx" + break; + case 327: /* "challenge" */ +#line 215 "parser.yxx" + { MEMMAN_DELETE((yyvaluep->yyt_challenge)); delete (yyvaluep->yyt_challenge); }; +#line 2235 "parser.cxx" + break; + + default: + break; + } +} + + +/* Prevent warnings from -Wmissing-prototypes. */ + +#ifdef YYPARSE_PARAM +#if defined __STDC__ || defined __cplusplus +int yyparse (void *YYPARSE_PARAM); +#else +int yyparse (); +#endif +#else /* ! YYPARSE_PARAM */ +#if defined __STDC__ || defined __cplusplus +int yyparse (void); +#else +int yyparse (); +#endif +#endif /* ! YYPARSE_PARAM */ + + + +/* The look-ahead symbol. */ +int yychar; + +/* The semantic value of the look-ahead symbol. */ +YYSTYPE yylval; + +/* Number of syntax errors so far. */ +int yynerrs; + + + +/*----------. +| yyparse. | +`----------*/ + +#ifdef YYPARSE_PARAM +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void *YYPARSE_PARAM) +#else +int +yyparse (YYPARSE_PARAM) + void *YYPARSE_PARAM; +#endif +#else /* ! YYPARSE_PARAM */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void) +#else +int +yyparse () + +#endif +#endif +{ + + int yystate; + int yyn; + int yyresult; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + /* Look-ahead token as an internal (translated) token number. */ + int yytoken = 0; +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + + /* Three stacks and their tools: + `yyss': related to states, + `yyvs': related to semantic values, + `yyls': related to locations. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss = yyssa; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs = yyvsa; + YYSTYPE *yyvsp; + + + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) + + YYSIZE_T yystacksize = YYINITDEPTH; + + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + + yyssp = yyss; + yyvsp = yyvs; + + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + + &yystacksize); + + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss); + YYSTACK_RELOCATE (yyvs); + +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + look-ahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to look-ahead token. */ + yyn = yypact[yystate]; + if (yyn == YYPACT_NINF) + goto yydefault; + + /* Not known => get a look-ahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yyn == 0 || yyn == YYTABLE_NINF) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + if (yyn == YYFINAL) + YYACCEPT; + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the look-ahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token unless it is eof. */ + if (yychar != YYEOF) + yychar = YYEMPTY; + + yystate = yyn; + *++yyvsp = yylval; + + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 2: +#line 244 "parser.yxx" + { CTXT_NEW; } + break; + + case 6: +#line 249 "parser.yxx" + { + /* 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; + } + break; + + case 7: +#line 269 "parser.yxx" + { + /* Parsing stops here. Remaining text is + * not parsed. + */ + YYACCEPT; } + break; + + case 8: +#line 278 "parser.yxx" + { CTXT_URI; } + break; + + case 9: +#line 278 "parser.yxx" + { CTXT_NEW; } + break; + + case 10: +#line 279 "parser.yxx" + { + MSG = new t_request(); + MEMMAN_NEW(MSG); + ((t_request *)MSG)->set_method(*(yyvsp[(1) - (6)].yyt_str)); + ((t_request *)MSG)->uri.set_url(*(yyvsp[(3) - (6)].yyt_str)); + MSG->version = *(yyvsp[(5) - (6)].yyt_str); + MEMMAN_DELETE((yyvsp[(1) - (6)].yyt_str)); delete (yyvsp[(1) - (6)].yyt_str); + MEMMAN_DELETE((yyvsp[(3) - (6)].yyt_str)); delete (yyvsp[(3) - (6)].yyt_str); + MEMMAN_DELETE((yyvsp[(5) - (6)].yyt_str)); delete (yyvsp[(5) - (6)].yyt_str); + + if (!((t_request *)MSG)->uri.is_valid()) { + MEMMAN_DELETE(MSG); delete MSG; + MSG = NULL; + YYABORT; + } } + break; + + case 11: +#line 296 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 12: +#line 296 "parser.yxx" + { + (yyval.yyt_str) = (yyvsp[(4) - (4)].yyt_str); } + break; + + case 13: +#line 300 "parser.yxx" + { + /* Parsing stops here. Remaining text is + * not parsed. + */ + YYACCEPT; } + break; + + case 14: +#line 307 "parser.yxx" + { CTXT_NUM; } + break; + + case 15: +#line 307 "parser.yxx" + { CTXT_LINE; } + break; + + case 16: +#line 308 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 17: +#line 308 "parser.yxx" + { + MSG = new t_response(); + MEMMAN_NEW(MSG); + MSG->version = *(yyvsp[(1) - (7)].yyt_str); + ((t_response *)MSG)->code = (yyvsp[(3) - (7)].yyt_ulong); + ((t_response *)MSG)->reason = trim(*(yyvsp[(5) - (7)].yyt_str)); + MEMMAN_DELETE((yyvsp[(1) - (7)].yyt_str)); delete (yyvsp[(1) - (7)].yyt_str); + MEMMAN_DELETE((yyvsp[(5) - (7)].yyt_str)); delete (yyvsp[(5) - (7)].yyt_str); } + break; + + case 80: +#line 382 "parser.yxx" + { + MSG->add_unknown_header(*(yyvsp[(1) - (4)].yyt_str), trim(*(yyvsp[(3) - (4)].yyt_str))); + MEMMAN_DELETE((yyvsp[(1) - (4)].yyt_str)); delete (yyvsp[(1) - (4)].yyt_str); + MEMMAN_DELETE((yyvsp[(3) - (4)].yyt_str)); delete (yyvsp[(3) - (4)].yyt_str); } + break; + + case 81: +#line 387 "parser.yxx" + { PARSE_ERROR("Accept"); } + break; + + case 82: +#line 389 "parser.yxx" + { PARSE_ERROR("Accept-Encoding"); } + break; + + case 83: +#line 391 "parser.yxx" + { PARSE_ERROR("Accept-Language"); } + break; + + case 84: +#line 393 "parser.yxx" + { PARSE_ERROR("Alert-Info"); } + break; + + case 85: +#line 395 "parser.yxx" + { PARSE_ERROR("Allow"); } + break; + + case 86: +#line 397 "parser.yxx" + { PARSE_ERROR("Allow-Events"); } + break; + + case 87: +#line 399 "parser.yxx" + { PARSE_ERROR("Authentication-Info"); } + break; + + case 88: +#line 401 "parser.yxx" + { PARSE_ERROR("Authorization"); } + break; + + case 89: +#line 403 "parser.yxx" + { PARSE_ERROR("Call-ID"); } + break; + + case 90: +#line 405 "parser.yxx" + { PARSE_ERROR("Call-Info"); } + break; + + case 91: +#line 407 "parser.yxx" + { PARSE_ERROR("Contact"); } + break; + + case 92: +#line 409 "parser.yxx" + { PARSE_ERROR("Content-Disposition"); } + break; + + case 93: +#line 411 "parser.yxx" + { PARSE_ERROR("Content-Encoding"); } + break; + + case 94: +#line 413 "parser.yxx" + { PARSE_ERROR("Content-Language"); } + break; + + case 95: +#line 415 "parser.yxx" + { PARSE_ERROR("Content-Length"); } + break; + + case 96: +#line 417 "parser.yxx" + { PARSE_ERROR("Content-Type"); } + break; + + case 97: +#line 419 "parser.yxx" + { PARSE_ERROR("CSeq"); } + break; + + case 98: +#line 421 "parser.yxx" + { PARSE_ERROR("Date"); } + break; + + case 99: +#line 423 "parser.yxx" + { PARSE_ERROR("Error-Info"); } + break; + + case 100: +#line 425 "parser.yxx" + { PARSE_ERROR("Event"); } + break; + + case 101: +#line 427 "parser.yxx" + { PARSE_ERROR("Expires"); } + break; + + case 102: +#line 429 "parser.yxx" + { PARSE_ERROR("From"); } + break; + + case 103: +#line 431 "parser.yxx" + { PARSE_ERROR("In-Reply-To"); } + break; + + case 104: +#line 433 "parser.yxx" + { PARSE_ERROR("Max-Forwards"); } + break; + + case 105: +#line 435 "parser.yxx" + { PARSE_ERROR("Min-Expires"); } + break; + + case 106: +#line 437 "parser.yxx" + { PARSE_ERROR("MIME-Version"); } + break; + + case 107: +#line 439 "parser.yxx" + { PARSE_ERROR("Organization"); } + break; + + case 108: +#line 441 "parser.yxx" + { PARSE_ERROR("P-Asserted-Identity"); } + break; + + case 109: +#line 443 "parser.yxx" + { PARSE_ERROR("P-Preferred-Identity"); } + break; + + case 110: +#line 445 "parser.yxx" + { PARSE_ERROR("Priority"); } + break; + + case 111: +#line 447 "parser.yxx" + { PARSE_ERROR("Privacy"); } + break; + + case 112: +#line 449 "parser.yxx" + { PARSE_ERROR("Proxy-Authenticate"); } + break; + + case 113: +#line 451 "parser.yxx" + { PARSE_ERROR("Proxy-Authorization"); } + break; + + case 114: +#line 453 "parser.yxx" + { PARSE_ERROR("Proxy-Require"); } + break; + + case 115: +#line 455 "parser.yxx" + { PARSE_ERROR("RAck"); } + break; + + case 116: +#line 457 "parser.yxx" + { PARSE_ERROR("Record-Route"); } + break; + + case 117: +#line 459 "parser.yxx" + { PARSE_ERROR("Refer-Sub"); } + break; + + case 118: +#line 461 "parser.yxx" + { PARSE_ERROR("Refer-To"); } + break; + + case 119: +#line 463 "parser.yxx" + { PARSE_ERROR("Referred-By"); } + break; + + case 120: +#line 465 "parser.yxx" + { PARSE_ERROR("Replaces"); } + break; + + case 121: +#line 467 "parser.yxx" + { PARSE_ERROR("Reply-To"); } + break; + + case 122: +#line 469 "parser.yxx" + { PARSE_ERROR("Require"); } + break; + + case 123: +#line 471 "parser.yxx" + { PARSE_ERROR("Request-Disposition"); } + break; + + case 124: +#line 473 "parser.yxx" + { PARSE_ERROR("Retry-After"); } + break; + + case 125: +#line 475 "parser.yxx" + { PARSE_ERROR("Route"); } + break; + + case 126: +#line 477 "parser.yxx" + { PARSE_ERROR("RSeq"); } + break; + + case 127: +#line 479 "parser.yxx" + { PARSE_ERROR("Server"); } + break; + + case 128: +#line 481 "parser.yxx" + { PARSE_ERROR("Service-Route"); } + break; + + case 129: +#line 483 "parser.yxx" + { PARSE_ERROR("SIP-ETag"); } + break; + + case 130: +#line 485 "parser.yxx" + { PARSE_ERROR("SIP-If-Match"); } + break; + + case 131: +#line 487 "parser.yxx" + { PARSE_ERROR("Subject"); } + break; + + case 132: +#line 489 "parser.yxx" + { PARSE_ERROR("Subscription-State"); } + break; + + case 133: +#line 491 "parser.yxx" + { PARSE_ERROR("Supported"); } + break; + + case 134: +#line 493 "parser.yxx" + { PARSE_ERROR("Timestamp"); } + break; + + case 135: +#line 495 "parser.yxx" + { PARSE_ERROR("To"); } + break; + + case 136: +#line 497 "parser.yxx" + { PARSE_ERROR("Unsupported"); } + break; + + case 137: +#line 499 "parser.yxx" + { PARSE_ERROR("User-Agent"); } + break; + + case 138: +#line 501 "parser.yxx" + { PARSE_ERROR("Via"); } + break; + + case 139: +#line 503 "parser.yxx" + { PARSE_ERROR("Warning"); } + break; + + case 140: +#line 505 "parser.yxx" + { PARSE_ERROR("WWW-Authenticate"); } + break; + + case 143: +#line 521 "parser.yxx" + { CTXT_LANG; } + break; + + case 148: +#line 531 "parser.yxx" + { CTXT_AUTH_SCHEME; } + break; + + case 149: +#line 533 "parser.yxx" + { CTXT_WORD; } + break; + + case 151: +#line 537 "parser.yxx" + { CTXT_URI_SPECIAL; } + break; + + case 154: +#line 543 "parser.yxx" + { CTXT_LANG; } + break; + + case 155: +#line 545 "parser.yxx" + { CTXT_NUM; } + break; + + case 157: +#line 549 "parser.yxx" + { CTXT_NUM; } + break; + + case 158: +#line 551 "parser.yxx" + { CTXT_DATE;} + break; + + case 161: +#line 557 "parser.yxx" + { CTXT_NUM; } + break; + + case 162: +#line 559 "parser.yxx" + { CTXT_URI_SPECIAL; } + break; + + case 163: +#line 561 "parser.yxx" + { CTXT_WORD; } + break; + + case 164: +#line 563 "parser.yxx" + { CTXT_NUM; } + break; + + case 165: +#line 565 "parser.yxx" + { CTXT_NUM; } + break; + + case 167: +#line 569 "parser.yxx" + { CTXT_LINE; } + break; + + case 168: +#line 571 "parser.yxx" + { CTXT_URI_SPECIAL; } + break; + + case 169: +#line 573 "parser.yxx" + { CTXT_URI_SPECIAL; } + break; + + case 172: +#line 579 "parser.yxx" + { CTXT_AUTH_SCHEME; } + break; + + case 173: +#line 581 "parser.yxx" + { CTXT_AUTH_SCHEME; } + break; + + case 175: +#line 585 "parser.yxx" + { CTXT_NUM; } + break; + + case 176: +#line 587 "parser.yxx" + { CTXT_URI; } + break; + + case 178: +#line 591 "parser.yxx" + { CTXT_URI_SPECIAL; } + break; + + case 179: +#line 593 "parser.yxx" + { CTXT_URI_SPECIAL; } + break; + + case 180: +#line 595 "parser.yxx" + { CTXT_WORD; } + break; + + case 181: +#line 597 "parser.yxx" + { CTXT_URI_SPECIAL; } + break; + + case 184: +#line 603 "parser.yxx" + { CTXT_NUM; } + break; + + case 185: +#line 605 "parser.yxx" + { CTXT_URI; } + break; + + case 186: +#line 607 "parser.yxx" + { CTXT_NUM; } + break; + + case 188: +#line 611 "parser.yxx" + { CTXT_URI; } + break; + + case 191: +#line 617 "parser.yxx" + { CTXT_LINE; } + break; + + case 194: +#line 623 "parser.yxx" + { CTXT_NUM; } + break; + + case 195: +#line 625 "parser.yxx" + { CTXT_URI_SPECIAL; } + break; + + case 199: +#line 633 "parser.yxx" + { CTXT_NUM; } + break; + + case 200: +#line 635 "parser.yxx" + { CTXT_AUTH_SCHEME; } + break; + + case 202: +#line 639 "parser.yxx" + { + (yyvsp[(1) - (2)].yyt_media)->add_params(*(yyvsp[(2) - (2)].yyt_params)); + MSG->hdr_accept.add_media(*(yyvsp[(1) - (2)].yyt_media)); + MEMMAN_DELETE((yyvsp[(1) - (2)].yyt_media)); delete (yyvsp[(1) - (2)].yyt_media); + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_params)); delete (yyvsp[(2) - (2)].yyt_params); } + break; + + case 203: +#line 644 "parser.yxx" + { + (yyvsp[(3) - (4)].yyt_media)->add_params(*(yyvsp[(4) - (4)].yyt_params)); + MSG->hdr_accept.add_media(*(yyvsp[(3) - (4)].yyt_media)); + MEMMAN_DELETE((yyvsp[(3) - (4)].yyt_media)); delete (yyvsp[(3) - (4)].yyt_media); + MEMMAN_DELETE((yyvsp[(4) - (4)].yyt_params)); delete (yyvsp[(4) - (4)].yyt_params); } + break; + + case 204: +#line 651 "parser.yxx" + { (yyval.yyt_media) = new t_media(tolower(*(yyvsp[(1) - (3)].yyt_str)), tolower(*(yyvsp[(3) - (3)].yyt_str))); + MEMMAN_NEW((yyval.yyt_media)); + MEMMAN_DELETE((yyvsp[(1) - (3)].yyt_str)); delete (yyvsp[(1) - (3)].yyt_str); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_str)); delete (yyvsp[(3) - (3)].yyt_str); } + break; + + case 205: +#line 657 "parser.yxx" + { (yyval.yyt_params) = new list; MEMMAN_NEW((yyval.yyt_params)); } + break; + + case 206: +#line 658 "parser.yxx" + { + (yyvsp[(1) - (3)].yyt_params)->push_back(*(yyvsp[(3) - (3)].yyt_param)); + (yyval.yyt_params) = (yyvsp[(1) - (3)].yyt_params); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_param)); delete (yyvsp[(3) - (3)].yyt_param); } + break; + + case 207: +#line 664 "parser.yxx" + { + (yyval.yyt_param) = new t_parameter(tolower(*(yyvsp[(1) - (1)].yyt_str))); + MEMMAN_NEW((yyval.yyt_param)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 208: +#line 668 "parser.yxx" + { CTXT_PARAMVAL; } + break; + + case 209: +#line 668 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 210: +#line 668 "parser.yxx" + { + (yyval.yyt_param) = new t_parameter(tolower(*(yyvsp[(1) - (5)].yyt_str)), *(yyvsp[(4) - (5)].yyt_str)); + MEMMAN_NEW((yyval.yyt_param)); + MEMMAN_DELETE((yyvsp[(1) - (5)].yyt_str)); delete (yyvsp[(1) - (5)].yyt_str); + MEMMAN_DELETE((yyvsp[(4) - (5)].yyt_str)); delete (yyvsp[(4) - (5)].yyt_str); } + break; + + case 211: +#line 675 "parser.yxx" + { + (yyval.yyt_str) = (yyvsp[(1) - (1)].yyt_str); } + break; + + case 212: +#line 677 "parser.yxx" + { + (yyval.yyt_str) = (yyvsp[(1) - (1)].yyt_str); } + break; + + case 213: +#line 681 "parser.yxx" + { + MSG->hdr_accept_encoding.add_coding(*(yyvsp[(1) - (1)].yyt_coding)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_coding)); delete (yyvsp[(1) - (1)].yyt_coding); } + break; + + case 214: +#line 684 "parser.yxx" + { + MSG->hdr_accept_encoding.add_coding(*(yyvsp[(3) - (3)].yyt_coding)); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_coding)); delete (yyvsp[(3) - (3)].yyt_coding); } + break; + + case 215: +#line 689 "parser.yxx" + { + (yyval.yyt_coding) = new t_coding(tolower(*(yyvsp[(1) - (1)].yyt_str))); + MEMMAN_NEW((yyval.yyt_coding)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 216: +#line 693 "parser.yxx" + { + (yyval.yyt_coding) = new t_coding(tolower(*(yyvsp[(1) - (2)].yyt_str))); + MEMMAN_NEW((yyval.yyt_coding)); + (yyval.yyt_coding)->q = (yyvsp[(2) - (2)].yyt_float); + MEMMAN_DELETE((yyvsp[(1) - (2)].yyt_str)); delete (yyvsp[(1) - (2)].yyt_str); } + break; + + case 217: +#line 700 "parser.yxx" + { + if ((yyvsp[(2) - (2)].yyt_param)->name != "q") YYERROR; + (yyval.yyt_float) = atof((yyvsp[(2) - (2)].yyt_param)->value.c_str()); + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_param)); delete (yyvsp[(2) - (2)].yyt_param); + } + break; + + case 218: +#line 707 "parser.yxx" + { CTXT_LANG; } + break; + + case 219: +#line 707 "parser.yxx" + { + MSG->hdr_accept_language.add_language(*(yyvsp[(2) - (2)].yyt_language)); + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_language)); delete (yyvsp[(2) - (2)].yyt_language); } + break; + + case 220: +#line 710 "parser.yxx" + { CTXT_LANG; } + break; + + case 221: +#line 710 "parser.yxx" + { + MSG->hdr_accept_language.add_language(*(yyvsp[(4) - (4)].yyt_language)); + MEMMAN_DELETE((yyvsp[(4) - (4)].yyt_language)); delete (yyvsp[(4) - (4)].yyt_language); } + break; + + case 222: +#line 715 "parser.yxx" + { + CTXT_INITIAL; + (yyval.yyt_language) = new t_language(tolower(*(yyvsp[(1) - (1)].yyt_str))); + MEMMAN_NEW((yyval.yyt_language)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 223: +#line 720 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 224: +#line 720 "parser.yxx" + { + (yyval.yyt_language) = new t_language(tolower(*(yyvsp[(1) - (3)].yyt_str))); + MEMMAN_NEW((yyval.yyt_language)); + (yyval.yyt_language)->q = (yyvsp[(3) - (3)].yyt_float); + MEMMAN_DELETE((yyvsp[(1) - (3)].yyt_str)); delete (yyvsp[(1) - (3)].yyt_str); } + break; + + case 225: +#line 727 "parser.yxx" + { + MSG->hdr_alert_info.add_param(*(yyvsp[(1) - (1)].yyt_alert_param)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_alert_param)); delete (yyvsp[(1) - (1)].yyt_alert_param); } + break; + + case 226: +#line 730 "parser.yxx" + { + MSG->hdr_alert_info.add_param(*(yyvsp[(3) - (3)].yyt_alert_param)); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_alert_param)); delete (yyvsp[(3) - (3)].yyt_alert_param); } + break; + + case 227: +#line 735 "parser.yxx" + { CTXT_URI; } + break; + + case 228: +#line 735 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 229: +#line 735 "parser.yxx" + { + (yyval.yyt_alert_param) = new t_alert_param(); + MEMMAN_NEW((yyval.yyt_alert_param)); + (yyval.yyt_alert_param)->uri.set_url(*(yyvsp[(3) - (6)].yyt_str)); + (yyval.yyt_alert_param)->parameter_list = *(yyvsp[(6) - (6)].yyt_params); + + if (!(yyval.yyt_alert_param)->uri.is_valid()) { + MEMMAN_DELETE((yyval.yyt_alert_param)); delete (yyval.yyt_alert_param); + YYERROR; + } + + MEMMAN_DELETE((yyvsp[(3) - (6)].yyt_str)); delete (yyvsp[(3) - (6)].yyt_str); + MEMMAN_DELETE((yyvsp[(6) - (6)].yyt_params)); delete (yyvsp[(6) - (6)].yyt_params); } + break; + + case 230: +#line 750 "parser.yxx" + { + MSG->hdr_allow.add_method(*(yyvsp[(1) - (1)].yyt_str)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 231: +#line 753 "parser.yxx" + { + MSG->hdr_allow.add_method(*(yyvsp[(3) - (3)].yyt_str)); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_str)); delete (yyvsp[(3) - (3)].yyt_str); } + break; + + case 232: +#line 758 "parser.yxx" + { CTXT_WORD; } + break; + + case 233: +#line 758 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 234: +#line 758 "parser.yxx" + { + MSG->hdr_call_id.set_call_id(*(yyvsp[(2) - (3)].yyt_str)); + MEMMAN_DELETE((yyvsp[(2) - (3)].yyt_str)); delete (yyvsp[(2) - (3)].yyt_str); } + break; + + case 235: +#line 763 "parser.yxx" + { (yyval.yyt_str) = (yyvsp[(1) - (1)].yyt_str); } + break; + + case 236: +#line 764 "parser.yxx" + { + (yyval.yyt_str) = new string(*(yyvsp[(1) - (3)].yyt_str) + '@' + *(yyvsp[(3) - (3)].yyt_str)); + MEMMAN_NEW((yyval.yyt_str)); + MEMMAN_DELETE((yyvsp[(1) - (3)].yyt_str)); delete (yyvsp[(1) - (3)].yyt_str); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_str)); delete (yyvsp[(3) - (3)].yyt_str); } + break; + + case 237: +#line 771 "parser.yxx" + { + MSG->hdr_call_info.add_param(*(yyvsp[(1) - (1)].yyt_info_param)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_info_param)); delete (yyvsp[(1) - (1)].yyt_info_param); } + break; + + case 238: +#line 774 "parser.yxx" + { + MSG->hdr_call_info.add_param(*(yyvsp[(3) - (3)].yyt_info_param)); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_info_param)); delete (yyvsp[(3) - (3)].yyt_info_param); } + break; + + case 239: +#line 779 "parser.yxx" + { CTXT_URI; } + break; + + case 240: +#line 779 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 241: +#line 779 "parser.yxx" + { + (yyval.yyt_info_param) = new t_info_param(); + MEMMAN_NEW((yyval.yyt_info_param)); + (yyval.yyt_info_param)->uri.set_url(*(yyvsp[(3) - (6)].yyt_str)); + (yyval.yyt_info_param)->parameter_list = *(yyvsp[(6) - (6)].yyt_params); + + if (!(yyval.yyt_info_param)->uri.is_valid()) { + MEMMAN_DELETE((yyval.yyt_info_param)); delete (yyval.yyt_info_param); + YYERROR; + } + + MEMMAN_DELETE((yyvsp[(3) - (6)].yyt_str)); delete (yyvsp[(3) - (6)].yyt_str); + MEMMAN_DELETE((yyvsp[(6) - (6)].yyt_params)); delete (yyvsp[(6) - (6)].yyt_params); } + break; + + case 242: +#line 794 "parser.yxx" + { CTXT_URI_SPECIAL; } + break; + + case 243: +#line 794 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 244: +#line 794 "parser.yxx" + { + MSG->hdr_contact.set_any(); } + break; + + case 245: +#line 796 "parser.yxx" + { + MSG->hdr_contact.add_contacts(*(yyvsp[(1) - (1)].yyt_contacts)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_contacts)); delete (yyvsp[(1) - (1)].yyt_contacts); } + break; + + case 246: +#line 801 "parser.yxx" + { + (yyval.yyt_contacts) = new list; + MEMMAN_NEW((yyval.yyt_contacts)); + (yyval.yyt_contacts)->push_back(*(yyvsp[(1) - (1)].yyt_contact)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_contact)); delete (yyvsp[(1) - (1)].yyt_contact); } + break; + + case 247: +#line 806 "parser.yxx" + { + (yyvsp[(1) - (3)].yyt_contacts)->push_back(*(yyvsp[(3) - (3)].yyt_contact)); + (yyval.yyt_contacts) = (yyvsp[(1) - (3)].yyt_contacts); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_contact)); delete (yyvsp[(3) - (3)].yyt_contact); } + break; + + case 248: +#line 812 "parser.yxx" + { + (yyval.yyt_contact) = (yyvsp[(1) - (2)].yyt_contact); + list::const_iterator i; + for (i = (yyvsp[(2) - (2)].yyt_params)->begin(); i != (yyvsp[(2) - (2)].yyt_params)->end(); i++) { + if (i->name == "q") { + (yyval.yyt_contact)->set_qvalue(atof(i->value.c_str())); + } else if (i->name == "expires") { + (yyval.yyt_contact)->set_expires(strtoul( + i->value.c_str(), NULL, 10)); + } else { + (yyval.yyt_contact)->add_extension(*i); + } + } + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_params)); delete (yyvsp[(2) - (2)].yyt_params); } + break; + + case 249: +#line 828 "parser.yxx" + { CTXT_URI_SPECIAL; } + break; + + case 250: +#line 828 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 251: +#line 828 "parser.yxx" + { + (yyval.yyt_contact) = new t_contact_param(); + MEMMAN_NEW((yyval.yyt_contact)); + (yyval.yyt_contact)->uri.set_url(*(yyvsp[(2) - (3)].yyt_str)); + + if (!(yyval.yyt_contact)->uri.is_valid()) { + MEMMAN_DELETE((yyval.yyt_contact)); delete (yyval.yyt_contact); + YYERROR; + } + + MEMMAN_DELETE((yyvsp[(2) - (3)].yyt_str)); delete (yyvsp[(2) - (3)].yyt_str); } + break; + + case 252: +#line 839 "parser.yxx" + { CTXT_URI_SPECIAL; } + break; + + case 253: +#line 839 "parser.yxx" + { CTXT_URI; } + break; + + case 254: +#line 839 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 255: +#line 839 "parser.yxx" + { + (yyval.yyt_contact) = new t_contact_param(); + MEMMAN_NEW((yyval.yyt_contact)); + (yyval.yyt_contact)->display = *(yyvsp[(2) - (7)].yyt_str); + (yyval.yyt_contact)->uri.set_url(*(yyvsp[(5) - (7)].yyt_str)); + + if (!(yyval.yyt_contact)->uri.is_valid()) { + MEMMAN_DELETE((yyval.yyt_contact)); delete (yyval.yyt_contact); + YYERROR; + } + + MEMMAN_DELETE((yyvsp[(2) - (7)].yyt_str)); delete (yyvsp[(2) - (7)].yyt_str); + MEMMAN_DELETE((yyvsp[(5) - (7)].yyt_str)); delete (yyvsp[(5) - (7)].yyt_str); } + break; + + case 256: +#line 854 "parser.yxx" + { (yyval.yyt_str) = new string(); MEMMAN_NEW((yyval.yyt_str)); } + break; + + case 257: +#line 855 "parser.yxx" + { + (yyval.yyt_str) = new string(rtrim(*(yyvsp[(1) - (1)].yyt_str))); + MEMMAN_NEW((yyval.yyt_str)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 258: +#line 859 "parser.yxx" + { (yyval.yyt_str) = (yyvsp[(1) - (1)].yyt_str); } + break; + + case 259: +#line 862 "parser.yxx" + { + MSG->hdr_content_disp.set_type(tolower(*(yyvsp[(1) - (2)].yyt_str))); + + list::const_iterator i; + for (i = (yyvsp[(2) - (2)].yyt_params)->begin(); i != (yyvsp[(2) - (2)].yyt_params)->end(); i++) { + if (i->name == "filename") { + MSG->hdr_content_disp.set_filename(i->value); + } else { + MSG->hdr_content_disp.add_param(*i); + } + } + + MEMMAN_DELETE((yyvsp[(1) - (2)].yyt_str)); delete (yyvsp[(1) - (2)].yyt_str); + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_params)); delete (yyvsp[(2) - (2)].yyt_params); } + break; + + case 260: +#line 878 "parser.yxx" + { + MSG->hdr_content_encoding.add_coding(*(yyvsp[(1) - (1)].yyt_coding)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_coding)); delete (yyvsp[(1) - (1)].yyt_coding); } + break; + + case 261: +#line 881 "parser.yxx" + { + MSG->hdr_content_encoding.add_coding(*(yyvsp[(3) - (3)].yyt_coding)); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_coding)); delete (yyvsp[(3) - (3)].yyt_coding); } + break; + + case 262: +#line 886 "parser.yxx" + { CTXT_LANG; } + break; + + case 263: +#line 886 "parser.yxx" + { + MSG->hdr_content_language.add_language(*(yyvsp[(2) - (2)].yyt_language)); + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_language)); delete (yyvsp[(2) - (2)].yyt_language); } + break; + + case 264: +#line 889 "parser.yxx" + { CTXT_LANG; } + break; + + case 265: +#line 889 "parser.yxx" + { + MSG->hdr_content_language.add_language(*(yyvsp[(4) - (4)].yyt_language)); + MEMMAN_DELETE((yyvsp[(4) - (4)].yyt_language)); delete (yyvsp[(4) - (4)].yyt_language); } + break; + + case 266: +#line 894 "parser.yxx" + { CTXT_NUM; } + break; + + case 267: +#line 894 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 268: +#line 894 "parser.yxx" + { + MSG->hdr_content_length.set_length((yyvsp[(2) - (3)].yyt_ulong)); } + break; + + case 269: +#line 898 "parser.yxx" + { + (yyvsp[(1) - (2)].yyt_media)->add_params(*(yyvsp[(2) - (2)].yyt_params)); + MSG->hdr_content_type.set_media(*(yyvsp[(1) - (2)].yyt_media)); + MEMMAN_DELETE((yyvsp[(1) - (2)].yyt_media)); delete (yyvsp[(1) - (2)].yyt_media); + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_params)); delete (yyvsp[(2) - (2)].yyt_params); } + break; + + case 270: +#line 905 "parser.yxx" + { CTXT_NUM; } + break; + + case 271: +#line 905 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 272: +#line 905 "parser.yxx" + { + MSG->hdr_cseq.set_seqnr((yyvsp[(2) - (4)].yyt_ulong)); + MSG->hdr_cseq.set_method(*(yyvsp[(4) - (4)].yyt_str)); + MEMMAN_DELETE((yyvsp[(4) - (4)].yyt_str)); delete (yyvsp[(4) - (4)].yyt_str); } + break; + + case 273: +#line 911 "parser.yxx" + { CTXT_DATE;} + break; + + case 274: +#line 914 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 275: +#line 914 "parser.yxx" + { + struct tm t; + t.tm_mday = (yyvsp[(4) - (13)].yyt_ulong); + t.tm_mon = (yyvsp[(5) - (13)].yyt_int); + t.tm_year = (yyvsp[(6) - (13)].yyt_ulong) - 1900; + t.tm_hour = (yyvsp[(7) - (13)].yyt_ulong); + t.tm_min = (yyvsp[(9) - (13)].yyt_ulong); + t.tm_sec = (yyvsp[(11) - (13)].yyt_ulong); + MSG->hdr_date.set_date_gm(&t); } + break; + + case 276: +#line 925 "parser.yxx" + { + MSG->hdr_error_info.add_param(*(yyvsp[(1) - (1)].yyt_error_param)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_error_param)); delete (yyvsp[(1) - (1)].yyt_error_param); } + break; + + case 277: +#line 928 "parser.yxx" + { + MSG->hdr_error_info.add_param(*(yyvsp[(3) - (3)].yyt_error_param)); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_error_param)); delete (yyvsp[(3) - (3)].yyt_error_param); } + break; + + case 278: +#line 933 "parser.yxx" + { CTXT_URI; } + break; + + case 279: +#line 933 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 280: +#line 933 "parser.yxx" + { + (yyval.yyt_error_param) = new t_error_param(); + MEMMAN_NEW((yyval.yyt_error_param)); + (yyval.yyt_error_param)->uri.set_url(*(yyvsp[(3) - (6)].yyt_str)); + (yyval.yyt_error_param)->parameter_list = *(yyvsp[(6) - (6)].yyt_params); + + if (!(yyval.yyt_error_param)->uri.is_valid()) { + MEMMAN_DELETE((yyval.yyt_error_param)); delete (yyval.yyt_error_param); + YYERROR; + } + + MEMMAN_DELETE((yyvsp[(3) - (6)].yyt_str)); delete (yyvsp[(3) - (6)].yyt_str); + MEMMAN_DELETE((yyvsp[(6) - (6)].yyt_params)); delete (yyvsp[(6) - (6)].yyt_params); } + break; + + case 281: +#line 948 "parser.yxx" + { CTXT_NUM; } + break; + + case 282: +#line 948 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 283: +#line 948 "parser.yxx" + { + MSG->hdr_expires.set_time((yyvsp[(2) - (3)].yyt_ulong)); } + break; + + case 284: +#line 952 "parser.yxx" + { CTXT_URI_SPECIAL; } + break; + + case 285: +#line 952 "parser.yxx" + { + MSG->hdr_from.set_display((yyvsp[(2) - (3)].yyt_from_addr)->display); + MSG->hdr_from.set_uri((yyvsp[(2) - (3)].yyt_from_addr)->uri); + list::const_iterator i; + for (i = (yyvsp[(3) - (3)].yyt_params)->begin(); i != (yyvsp[(3) - (3)].yyt_params)->end(); i++) { + if (i->name == "tag") { + MSG->hdr_from.set_tag(i->value); + } else { + MSG->hdr_from.add_param(*i); + } + } + MEMMAN_DELETE((yyvsp[(2) - (3)].yyt_from_addr)); delete (yyvsp[(2) - (3)].yyt_from_addr); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_params)); delete (yyvsp[(3) - (3)].yyt_params); } + break; + + case 286: +#line 967 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 287: +#line 967 "parser.yxx" + { + (yyval.yyt_from_addr) = new t_identity(); + MEMMAN_NEW((yyval.yyt_from_addr)); + (yyval.yyt_from_addr)->set_uri(*(yyvsp[(1) - (2)].yyt_str)); + + if (!(yyval.yyt_from_addr)->uri.is_valid()) { + MEMMAN_DELETE((yyval.yyt_from_addr)); delete (yyval.yyt_from_addr); + YYERROR; + } + + MEMMAN_DELETE((yyvsp[(1) - (2)].yyt_str)); delete (yyvsp[(1) - (2)].yyt_str); } + break; + + case 288: +#line 978 "parser.yxx" + { CTXT_URI; } + break; + + case 289: +#line 978 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 290: +#line 978 "parser.yxx" + { + (yyval.yyt_from_addr) = new t_identity(); + MEMMAN_NEW((yyval.yyt_from_addr)); + (yyval.yyt_from_addr)->set_display(*(yyvsp[(1) - (6)].yyt_str)); + (yyval.yyt_from_addr)->set_uri(*(yyvsp[(4) - (6)].yyt_str)); + + if (!(yyval.yyt_from_addr)->uri.is_valid()) { + MEMMAN_DELETE((yyval.yyt_from_addr)); delete (yyval.yyt_from_addr); + YYERROR; + } + + MEMMAN_DELETE((yyvsp[(1) - (6)].yyt_str)); delete (yyvsp[(1) - (6)].yyt_str); + MEMMAN_DELETE((yyvsp[(4) - (6)].yyt_str)); delete (yyvsp[(4) - (6)].yyt_str); } + break; + + case 291: +#line 993 "parser.yxx" + { CTXT_WORD; } + break; + + case 292: +#line 993 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 293: +#line 993 "parser.yxx" + { + MSG->hdr_in_reply_to.add_call_id(*(yyvsp[(2) - (3)].yyt_str)); + MEMMAN_DELETE((yyvsp[(2) - (3)].yyt_str)); delete (yyvsp[(2) - (3)].yyt_str); } + break; + + case 294: +#line 996 "parser.yxx" + { CTXT_WORD; } + break; + + case 295: +#line 996 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 296: +#line 996 "parser.yxx" + { + MSG->hdr_in_reply_to.add_call_id(*(yyvsp[(4) - (5)].yyt_str)); + MEMMAN_DELETE((yyvsp[(4) - (5)].yyt_str)); delete (yyvsp[(4) - (5)].yyt_str); } + break; + + case 297: +#line 1001 "parser.yxx" + { CTXT_NUM; } + break; + + case 298: +#line 1001 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 299: +#line 1001 "parser.yxx" + { + MSG->hdr_max_forwards.set_max_forwards((yyvsp[(2) - (3)].yyt_ulong)); } + break; + + case 300: +#line 1005 "parser.yxx" + { CTXT_NUM; } + break; + + case 301: +#line 1005 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 302: +#line 1005 "parser.yxx" + { + MSG->hdr_min_expires.set_time((yyvsp[(2) - (3)].yyt_ulong)); } + break; + + case 303: +#line 1009 "parser.yxx" + { + MSG->hdr_mime_version.set_version(*(yyvsp[(1) - (1)].yyt_str)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 304: +#line 1014 "parser.yxx" + { CTXT_LINE; } + break; + + case 305: +#line 1014 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 306: +#line 1014 "parser.yxx" + { + MSG->hdr_organization.set_name(trim(*(yyvsp[(2) - (3)].yyt_str))); + MEMMAN_DELETE((yyvsp[(2) - (3)].yyt_str)); delete (yyvsp[(2) - (3)].yyt_str); } + break; + + case 307: +#line 1019 "parser.yxx" + { CTXT_URI_SPECIAL; } + break; + + case 308: +#line 1019 "parser.yxx" + { + MSG->hdr_p_asserted_identity.add_identity(*(yyvsp[(2) - (2)].yyt_from_addr)); + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_from_addr)); delete (yyvsp[(2) - (2)].yyt_from_addr); } + break; + + case 309: +#line 1022 "parser.yxx" + { + MSG->hdr_p_asserted_identity.add_identity(*(yyvsp[(3) - (3)].yyt_from_addr)); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_from_addr)); delete (yyvsp[(3) - (3)].yyt_from_addr); } + break; + + case 310: +#line 1027 "parser.yxx" + { CTXT_URI_SPECIAL; } + break; + + case 311: +#line 1027 "parser.yxx" + { + MSG->hdr_p_preferred_identity.add_identity(*(yyvsp[(2) - (2)].yyt_from_addr)); + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_from_addr)); delete (yyvsp[(2) - (2)].yyt_from_addr); } + break; + + case 312: +#line 1030 "parser.yxx" + { + MSG->hdr_p_preferred_identity.add_identity(*(yyvsp[(3) - (3)].yyt_from_addr)); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_from_addr)); delete (yyvsp[(3) - (3)].yyt_from_addr); } + break; + + case 313: +#line 1035 "parser.yxx" + { + MSG->hdr_priority.set_priority(tolower(*(yyvsp[(1) - (1)].yyt_str))); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 314: +#line 1040 "parser.yxx" + { + MSG->hdr_privacy.add_privacy(tolower(*(yyvsp[(1) - (1)].yyt_str))); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 315: +#line 1043 "parser.yxx" + { + MSG->hdr_privacy.add_privacy(tolower(*(yyvsp[(3) - (3)].yyt_str))); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_str)); delete (yyvsp[(3) - (3)].yyt_str); } + break; + + case 316: +#line 1048 "parser.yxx" + { + MSG->hdr_proxy_require.add_feature(tolower(*(yyvsp[(1) - (1)].yyt_str))); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 317: +#line 1051 "parser.yxx" + { + MSG->hdr_proxy_require.add_feature(tolower(*(yyvsp[(3) - (3)].yyt_str))); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_str)); delete (yyvsp[(3) - (3)].yyt_str); } + break; + + case 318: +#line 1056 "parser.yxx" + { + MSG->hdr_record_route.add_route(*(yyvsp[(1) - (1)].yyt_route)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_route)); delete (yyvsp[(1) - (1)].yyt_route); } + break; + + case 319: +#line 1059 "parser.yxx" + { + MSG->hdr_record_route.add_route(*(yyvsp[(3) - (3)].yyt_route)); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_route)); delete (yyvsp[(3) - (3)].yyt_route); } + break; + + case 320: +#line 1064 "parser.yxx" + { CTXT_URI; } + break; + + case 321: +#line 1064 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 322: +#line 1065 "parser.yxx" + { + (yyval.yyt_route) = new t_route; + MEMMAN_NEW((yyval.yyt_route)); + (yyval.yyt_route)->display = *(yyvsp[(2) - (7)].yyt_str); + (yyval.yyt_route)->uri.set_url(*(yyvsp[(4) - (7)].yyt_str)); + (yyval.yyt_route)->set_params(*(yyvsp[(7) - (7)].yyt_params)); + + if (!(yyval.yyt_route)->uri.is_valid()) { + MEMMAN_DELETE((yyval.yyt_route)); delete (yyval.yyt_route); + YYERROR; + } + + MEMMAN_DELETE((yyvsp[(2) - (7)].yyt_str)); delete (yyvsp[(2) - (7)].yyt_str); + MEMMAN_DELETE((yyvsp[(4) - (7)].yyt_str)); delete (yyvsp[(4) - (7)].yyt_str); + MEMMAN_DELETE((yyvsp[(7) - (7)].yyt_params)); delete (yyvsp[(7) - (7)].yyt_params); } + break; + + case 323: +#line 1082 "parser.yxx" + { + MSG->hdr_service_route.add_route(*(yyvsp[(1) - (1)].yyt_route)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_route)); delete (yyvsp[(1) - (1)].yyt_route); } + break; + + case 324: +#line 1085 "parser.yxx" + { + MSG->hdr_service_route.add_route(*(yyvsp[(3) - (3)].yyt_route)); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_route)); delete (yyvsp[(3) - (3)].yyt_route); } + break; + + case 325: +#line 1090 "parser.yxx" + { CTXT_WORD; } + break; + + case 326: +#line 1090 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 327: +#line 1090 "parser.yxx" + { + MSG->hdr_replaces.set_call_id(*(yyvsp[(2) - (4)].yyt_str)); + + list::const_iterator i; + for (i = (yyvsp[(4) - (4)].yyt_params)->begin(); i != (yyvsp[(4) - (4)].yyt_params)->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((yyvsp[(2) - (4)].yyt_str)); delete (yyvsp[(2) - (4)].yyt_str); + MEMMAN_DELETE((yyvsp[(4) - (4)].yyt_params)); delete (yyvsp[(4) - (4)].yyt_params); } + break; + + case 328: +#line 1112 "parser.yxx" + { CTXT_URI_SPECIAL; } + break; + + case 329: +#line 1112 "parser.yxx" + { + MSG->hdr_reply_to.set_display((yyvsp[(2) - (3)].yyt_from_addr)->display); + MSG->hdr_reply_to.set_uri((yyvsp[(2) - (3)].yyt_from_addr)->uri); + MSG->hdr_reply_to.set_params(*(yyvsp[(3) - (3)].yyt_params)); + MEMMAN_DELETE((yyvsp[(2) - (3)].yyt_from_addr)); delete (yyvsp[(2) - (3)].yyt_from_addr); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_params)); delete (yyvsp[(3) - (3)].yyt_params); } + break; + + case 330: +#line 1120 "parser.yxx" + { + MSG->hdr_require.add_feature(tolower(*(yyvsp[(1) - (1)].yyt_str))); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 331: +#line 1123 "parser.yxx" + { + MSG->hdr_require.add_feature(tolower(*(yyvsp[(3) - (3)].yyt_str))); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_str)); delete (yyvsp[(3) - (3)].yyt_str); } + break; + + case 332: +#line 1128 "parser.yxx" + { CTXT_NUM; } + break; + + case 333: +#line 1128 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 334: +#line 1128 "parser.yxx" + { + MSG->hdr_retry_after.set_time((yyvsp[(2) - (5)].yyt_ulong)); + MSG->hdr_retry_after.set_comment(*(yyvsp[(4) - (5)].yyt_str)); + list::const_iterator i; + for (i = (yyvsp[(5) - (5)].yyt_params)->begin(); i != (yyvsp[(5) - (5)].yyt_params)->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((yyvsp[(4) - (5)].yyt_str)); delete (yyvsp[(4) - (5)].yyt_str); + MEMMAN_DELETE((yyvsp[(5) - (5)].yyt_params)); delete (yyvsp[(5) - (5)].yyt_params); } + break; + + case 335: +#line 1144 "parser.yxx" + { (yyval.yyt_str) = new string(); MEMMAN_NEW((yyval.yyt_str)); } + break; + + case 336: +#line 1145 "parser.yxx" + { CTXT_COMMENT; } + break; + + case 337: +#line 1145 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 338: +#line 1145 "parser.yxx" + { + (yyval.yyt_str) = (yyvsp[(3) - (5)].yyt_str); } + break; + + case 339: +#line 1149 "parser.yxx" + { + MSG->hdr_route.add_route(*(yyvsp[(1) - (1)].yyt_route)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_route)); delete (yyvsp[(1) - (1)].yyt_route); } + break; + + case 340: +#line 1152 "parser.yxx" + { + MSG->hdr_route.add_route(*(yyvsp[(3) - (3)].yyt_route)); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_route)); delete (yyvsp[(3) - (3)].yyt_route); } + break; + + case 341: +#line 1157 "parser.yxx" + { + MSG->hdr_server.add_server(*(yyvsp[(1) - (1)].yyt_server)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_server)); delete (yyvsp[(1) - (1)].yyt_server); } + break; + + case 342: +#line 1160 "parser.yxx" + { + MSG->hdr_server.add_server(*(yyvsp[(2) - (2)].yyt_server)); + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_server)); delete (yyvsp[(2) - (2)].yyt_server); } + break; + + case 343: +#line 1165 "parser.yxx" + { + (yyval.yyt_server) = new t_server(); + MEMMAN_NEW((yyval.yyt_server)); + (yyval.yyt_server)->comment = *(yyvsp[(1) - (1)].yyt_str); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 344: +#line 1170 "parser.yxx" + { + (yyval.yyt_server) = new t_server(); + MEMMAN_NEW((yyval.yyt_server)); + (yyval.yyt_server)->product = *(yyvsp[(1) - (2)].yyt_str); + (yyval.yyt_server)->comment = *(yyvsp[(2) - (2)].yyt_str); + MEMMAN_DELETE((yyvsp[(1) - (2)].yyt_str)); delete (yyvsp[(1) - (2)].yyt_str); + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_str)); delete (yyvsp[(2) - (2)].yyt_str); } + break; + + case 345: +#line 1177 "parser.yxx" + { + (yyval.yyt_server) = new t_server(); + MEMMAN_NEW((yyval.yyt_server)); + (yyval.yyt_server)->product = *(yyvsp[(1) - (4)].yyt_str); + (yyval.yyt_server)->version = *(yyvsp[(3) - (4)].yyt_str); + (yyval.yyt_server)->comment = *(yyvsp[(4) - (4)].yyt_str); + MEMMAN_DELETE((yyvsp[(1) - (4)].yyt_str)); delete (yyvsp[(1) - (4)].yyt_str); + MEMMAN_DELETE((yyvsp[(3) - (4)].yyt_str)); delete (yyvsp[(3) - (4)].yyt_str); + MEMMAN_DELETE((yyvsp[(4) - (4)].yyt_str)); delete (yyvsp[(4) - (4)].yyt_str); } + break; + + case 346: +#line 1188 "parser.yxx" + { CTXT_LINE; } + break; + + case 347: +#line 1188 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 348: +#line 1188 "parser.yxx" + { + MSG->hdr_subject.set_subject(trim(*(yyvsp[(2) - (3)].yyt_str))); + MEMMAN_DELETE((yyvsp[(2) - (3)].yyt_str)); delete (yyvsp[(2) - (3)].yyt_str); } + break; + + case 349: +#line 1193 "parser.yxx" + { + MSG->hdr_supported.set_empty(); } + break; + + case 350: +#line 1195 "parser.yxx" + { + MSG->hdr_supported.add_feature(tolower(*(yyvsp[(1) - (1)].yyt_str))); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 351: +#line 1198 "parser.yxx" + { + MSG->hdr_supported.add_feature(tolower(*(yyvsp[(3) - (3)].yyt_str))); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_str)); delete (yyvsp[(3) - (3)].yyt_str); } + break; + + case 352: +#line 1203 "parser.yxx" + { CTXT_NUM; } + break; + + case 353: +#line 1203 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 354: +#line 1206 "parser.yxx" + { + MSG->hdr_timestamp.set_timestamp((yyvsp[(1) - (1)].yyt_float)); } + break; + + case 355: +#line 1208 "parser.yxx" + { + MSG->hdr_timestamp.set_timestamp((yyvsp[(1) - (2)].yyt_float)); + MSG->hdr_timestamp.set_delay((yyvsp[(2) - (2)].yyt_float)); } + break; + + case 356: +#line 1213 "parser.yxx" + { (yyval.yyt_float) = (yyvsp[(1) - (1)].yyt_ulong); } + break; + + case 357: +#line 1214 "parser.yxx" + { + string s = int2str((yyvsp[(1) - (3)].yyt_ulong)) + '.' + int2str((yyvsp[(3) - (3)].yyt_ulong)); + (yyval.yyt_float) = atof(s.c_str()); } + break; + + case 358: +#line 1219 "parser.yxx" + { (yyval.yyt_float) = (yyvsp[(1) - (1)].yyt_ulong); } + break; + + case 359: +#line 1220 "parser.yxx" + { + string s = int2str((yyvsp[(1) - (3)].yyt_ulong)) + '.' + int2str((yyvsp[(3) - (3)].yyt_ulong)); + (yyval.yyt_float) = atof(s.c_str()); } + break; + + case 360: +#line 1225 "parser.yxx" + { CTXT_URI_SPECIAL; } + break; + + case 361: +#line 1225 "parser.yxx" + { + MSG->hdr_to.set_display((yyvsp[(2) - (3)].yyt_from_addr)->display); + MSG->hdr_to.set_uri((yyvsp[(2) - (3)].yyt_from_addr)->uri); + list::const_iterator i; + for (i = (yyvsp[(3) - (3)].yyt_params)->begin(); i != (yyvsp[(3) - (3)].yyt_params)->end(); i++) { + if (i->name == "tag") { + MSG->hdr_to.set_tag(i->value); + } else { + MSG->hdr_to.add_param(*i); + } + } + MEMMAN_DELETE((yyvsp[(2) - (3)].yyt_from_addr)); delete (yyvsp[(2) - (3)].yyt_from_addr); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_params)); delete (yyvsp[(3) - (3)].yyt_params); } + break; + + case 362: +#line 1240 "parser.yxx" + { + MSG->hdr_unsupported.add_feature(tolower(*(yyvsp[(1) - (1)].yyt_str))); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 363: +#line 1243 "parser.yxx" + { + MSG->hdr_unsupported.add_feature(tolower(*(yyvsp[(3) - (3)].yyt_str))); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_str)); delete (yyvsp[(3) - (3)].yyt_str); } + break; + + case 364: +#line 1248 "parser.yxx" + { + MSG->hdr_user_agent.add_server(*(yyvsp[(1) - (1)].yyt_server)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_server)); delete (yyvsp[(1) - (1)].yyt_server); } + break; + + case 365: +#line 1251 "parser.yxx" + { + MSG->hdr_user_agent.add_server(*(yyvsp[(2) - (2)].yyt_server)); + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_server)); delete (yyvsp[(2) - (2)].yyt_server); } + break; + + case 366: +#line 1256 "parser.yxx" + { + MSG->hdr_via.add_via(*(yyvsp[(1) - (1)].yyt_via)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_via)); delete (yyvsp[(1) - (1)].yyt_via); } + break; + + case 367: +#line 1259 "parser.yxx" + { + MSG->hdr_via.add_via(*(yyvsp[(3) - (3)].yyt_via)); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_via)); delete (yyvsp[(3) - (3)].yyt_via); } + break; + + case 368: +#line 1264 "parser.yxx" + { + (yyval.yyt_via) = (yyvsp[(1) - (3)].yyt_via); + (yyval.yyt_via)->host = (yyvsp[(2) - (3)].yyt_via)->host; + (yyval.yyt_via)->port = (yyvsp[(2) - (3)].yyt_via)->port; + list::const_iterator i; + for (i = (yyvsp[(3) - (3)].yyt_params)->begin(); i != (yyvsp[(3) - (3)].yyt_params)->end(); i++) { + if (i->name == "ttl") { + (yyval.yyt_via)->ttl = atoi(i->value.c_str()); + } else if (i->name == "maddr") { + (yyval.yyt_via)->maddr = i->value; + } else if (i->name == "received") { + (yyval.yyt_via)->received = i->value; + } else if (i->name == "branch") { + (yyval.yyt_via)->branch = i->value; + } else if (i->name == "rport") { + (yyval.yyt_via)->rport_present = true; + if (i->type == t_parameter::VALUE) { + (yyval.yyt_via)->rport = + atoi(i->value.c_str()); + } + } else { + (yyval.yyt_via)->add_extension(*i); + } + } + MEMMAN_DELETE((yyvsp[(2) - (3)].yyt_via)); delete (yyvsp[(2) - (3)].yyt_via); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_params)); delete (yyvsp[(3) - (3)].yyt_params); } + break; + + case 369: +#line 1292 "parser.yxx" + { + (yyval.yyt_via) = new t_via(); + MEMMAN_NEW((yyval.yyt_via)); + (yyval.yyt_via)->protocol_name = toupper(*(yyvsp[(1) - (5)].yyt_str)); + (yyval.yyt_via)->protocol_version = *(yyvsp[(3) - (5)].yyt_str); + (yyval.yyt_via)->transport = toupper(*(yyvsp[(5) - (5)].yyt_str)); + MEMMAN_DELETE((yyvsp[(1) - (5)].yyt_str)); delete (yyvsp[(1) - (5)].yyt_str); + MEMMAN_DELETE((yyvsp[(3) - (5)].yyt_str)); delete (yyvsp[(3) - (5)].yyt_str); + MEMMAN_DELETE((yyvsp[(5) - (5)].yyt_str)); delete (yyvsp[(5) - (5)].yyt_str); } + break; + + case 370: +#line 1303 "parser.yxx" + { + (yyval.yyt_via) = new t_via(); + MEMMAN_NEW((yyval.yyt_via)); + (yyval.yyt_via)->host = *(yyvsp[(1) - (1)].yyt_str); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 371: +#line 1308 "parser.yxx" + { CTXT_NUM; } + break; + + case 372: +#line 1308 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 373: +#line 1308 "parser.yxx" + { + if ((yyvsp[(4) - (5)].yyt_ulong) > 65535) YYERROR; + + (yyval.yyt_via) = new t_via(); + MEMMAN_NEW((yyval.yyt_via)); + (yyval.yyt_via)->host = *(yyvsp[(1) - (5)].yyt_str); + (yyval.yyt_via)->port = (yyvsp[(4) - (5)].yyt_ulong); + MEMMAN_DELETE((yyvsp[(1) - (5)].yyt_str)); delete (yyvsp[(1) - (5)].yyt_str); } + break; + + case 374: +#line 1316 "parser.yxx" + { + (yyval.yyt_via) = new t_via(); + MEMMAN_NEW((yyval.yyt_via)); + (yyval.yyt_via)->host = *(yyvsp[(1) - (1)].yyt_str); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 375: +#line 1321 "parser.yxx" + { CTXT_NUM; } + break; + + case 376: +#line 1321 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 377: +#line 1321 "parser.yxx" + { + (yyval.yyt_via) = new t_via(); + MEMMAN_NEW((yyval.yyt_via)); + (yyval.yyt_via)->host = *(yyvsp[(1) - (5)].yyt_str); + (yyval.yyt_via)->port = (yyvsp[(4) - (5)].yyt_ulong); + MEMMAN_DELETE((yyvsp[(1) - (5)].yyt_str)); delete (yyvsp[(1) - (5)].yyt_str); } + break; + + case 378: +#line 1329 "parser.yxx" + { CTXT_IPV6ADDR; } + break; + + case 379: +#line 1329 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 380: +#line 1329 "parser.yxx" + { + // TODO: check correct format of IPv6 address + (yyval.yyt_str) = new string('[' + *(yyvsp[(3) - (5)].yyt_str) + ']'); + MEMMAN_NEW((yyval.yyt_str)); + MEMMAN_DELETE((yyvsp[(3) - (5)].yyt_str)); } + break; + + case 381: +#line 1336 "parser.yxx" + { + MSG->hdr_warning.add_warning(*(yyvsp[(1) - (1)].yyt_warning)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_warning)); delete (yyvsp[(1) - (1)].yyt_warning); } + break; + + case 382: +#line 1339 "parser.yxx" + { + MSG->hdr_warning.add_warning(*(yyvsp[(3) - (3)].yyt_warning)); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_warning)); delete (yyvsp[(3) - (3)].yyt_warning); } + break; + + case 383: +#line 1344 "parser.yxx" + { CTXT_NUM; } + break; + + case 384: +#line 1344 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 385: +#line 1344 "parser.yxx" + { + (yyval.yyt_warning) = new t_warning(); + MEMMAN_NEW((yyval.yyt_warning)); + (yyval.yyt_warning)->code = (yyvsp[(2) - (5)].yyt_ulong); + (yyval.yyt_warning)->host = (yyvsp[(4) - (5)].yyt_via)->host; + (yyval.yyt_warning)->port = (yyvsp[(4) - (5)].yyt_via)->port; + (yyval.yyt_warning)->text = *(yyvsp[(5) - (5)].yyt_str); + MEMMAN_DELETE((yyvsp[(4) - (5)].yyt_via)); delete (yyvsp[(4) - (5)].yyt_via); + MEMMAN_DELETE((yyvsp[(5) - (5)].yyt_str)); delete (yyvsp[(5) - (5)].yyt_str); } + break; + + case 386: +#line 1355 "parser.yxx" + { CTXT_LINE; } + break; + + case 387: +#line 1355 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 388: +#line 1355 "parser.yxx" + { (yyval.yyt_str) = (yyvsp[(2) - (3)].yyt_str); } + break; + + case 389: +#line 1358 "parser.yxx" + { + if ((yyvsp[(1) - (1)].yyt_param)->name == "nextnonce") + MSG->hdr_auth_info.set_next_nonce((yyvsp[(1) - (1)].yyt_param)->value); + else if ((yyvsp[(1) - (1)].yyt_param)->name == "qop") + MSG->hdr_auth_info.set_message_qop((yyvsp[(1) - (1)].yyt_param)->value); + else if ((yyvsp[(1) - (1)].yyt_param)->name == "rspauth") + MSG->hdr_auth_info.set_response_auth((yyvsp[(1) - (1)].yyt_param)->value); + else if ((yyvsp[(1) - (1)].yyt_param)->name == "cnonce") + MSG->hdr_auth_info.set_cnonce((yyvsp[(1) - (1)].yyt_param)->value); + else if ((yyvsp[(1) - (1)].yyt_param)->name == "nc") { + MSG->hdr_auth_info.set_nonce_count( + hex2int((yyvsp[(1) - (1)].yyt_param)->value)); + } + else { + YYERROR; + } + + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_param)); delete (yyvsp[(1) - (1)].yyt_param); } + break; + + case 392: +#line 1382 "parser.yxx" + { + (yyval.yyt_dig_resp) = new t_digest_response(); + MEMMAN_NEW((yyval.yyt_dig_resp)); + if (!(yyval.yyt_dig_resp)->set_attr(*(yyvsp[(1) - (1)].yyt_param))) { + MEMMAN_DELETE((yyval.yyt_dig_resp)); delete (yyval.yyt_dig_resp); + YYERROR; + } + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_param)); delete (yyvsp[(1) - (1)].yyt_param); } + break; + + case 393: +#line 1390 "parser.yxx" + { + (yyval.yyt_dig_resp) = (yyvsp[(1) - (3)].yyt_dig_resp); + if (!(yyval.yyt_dig_resp)->set_attr(*(yyvsp[(3) - (3)].yyt_param))) { + YYERROR; + } + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_param)); delete (yyvsp[(3) - (3)].yyt_param); } + break; + + case 394: +#line 1398 "parser.yxx" + { + (yyval.yyt_params) = new list; + MEMMAN_NEW((yyval.yyt_params)); + (yyval.yyt_params)->push_back(*(yyvsp[(1) - (1)].yyt_param)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_param)); delete (yyvsp[(1) - (1)].yyt_param); } + break; + + case 395: +#line 1403 "parser.yxx" + { + (yyval.yyt_params) = (yyvsp[(1) - (3)].yyt_params); + (yyval.yyt_params)->push_back(*(yyvsp[(3) - (3)].yyt_param)); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_param)); delete (yyvsp[(3) - (3)].yyt_param); } + break; + + case 396: +#line 1409 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 397: +#line 1409 "parser.yxx" + { + (yyval.yyt_credentials) = new t_credentials; + MEMMAN_NEW((yyval.yyt_credentials)); + (yyval.yyt_credentials)->auth_scheme = AUTH_DIGEST; + (yyval.yyt_credentials)->digest_response = *(yyvsp[(3) - (3)].yyt_dig_resp); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_dig_resp)); delete (yyvsp[(3) - (3)].yyt_dig_resp); } + break; + + case 398: +#line 1415 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 399: +#line 1415 "parser.yxx" + { + (yyval.yyt_credentials) = new t_credentials; + MEMMAN_NEW((yyval.yyt_credentials)); + (yyval.yyt_credentials)->auth_scheme = *(yyvsp[(1) - (3)].yyt_str); + (yyval.yyt_credentials)->auth_params = *(yyvsp[(3) - (3)].yyt_params); + MEMMAN_DELETE((yyvsp[(1) - (3)].yyt_str)); delete (yyvsp[(1) - (3)].yyt_str); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_params)); delete (yyvsp[(3) - (3)].yyt_params); } + break; + + case 400: +#line 1424 "parser.yxx" + { CTXT_AUTH_SCHEME; } + break; + + case 401: +#line 1424 "parser.yxx" + { + MSG->hdr_authorization.add_credentials(*(yyvsp[(2) - (2)].yyt_credentials)); + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_credentials)); delete (yyvsp[(2) - (2)].yyt_credentials); } + break; + + case 402: +#line 1429 "parser.yxx" + { + (yyval.yyt_dig_chlg) = new t_digest_challenge(); + MEMMAN_NEW((yyval.yyt_dig_chlg)); + if (!(yyval.yyt_dig_chlg)->set_attr(*(yyvsp[(1) - (1)].yyt_param))) { + MEMMAN_DELETE((yyval.yyt_dig_chlg)); delete (yyval.yyt_dig_chlg); + YYERROR; + } + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_param)); delete (yyvsp[(1) - (1)].yyt_param); } + break; + + case 403: +#line 1437 "parser.yxx" + { + (yyval.yyt_dig_chlg) = (yyvsp[(1) - (3)].yyt_dig_chlg); + if (!(yyval.yyt_dig_chlg)->set_attr(*(yyvsp[(3) - (3)].yyt_param))) { + YYERROR; + } + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_param)); delete (yyvsp[(3) - (3)].yyt_param); } + break; + + case 404: +#line 1445 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 405: +#line 1445 "parser.yxx" + { + (yyval.yyt_challenge) = new t_challenge; + MEMMAN_NEW((yyval.yyt_challenge)); + (yyval.yyt_challenge)->auth_scheme = AUTH_DIGEST; + (yyval.yyt_challenge)->digest_challenge = *(yyvsp[(3) - (3)].yyt_dig_chlg); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_dig_chlg)); delete (yyvsp[(3) - (3)].yyt_dig_chlg); } + break; + + case 406: +#line 1451 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 407: +#line 1451 "parser.yxx" + { + (yyval.yyt_challenge) = new t_challenge; + MEMMAN_NEW((yyval.yyt_challenge)); + (yyval.yyt_challenge)->auth_scheme = *(yyvsp[(1) - (3)].yyt_str); + (yyval.yyt_challenge)->auth_params = *(yyvsp[(3) - (3)].yyt_params); + MEMMAN_DELETE((yyvsp[(1) - (3)].yyt_str)); delete (yyvsp[(1) - (3)].yyt_str); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_params)); delete (yyvsp[(3) - (3)].yyt_params); } + break; + + case 408: +#line 1460 "parser.yxx" + { CTXT_AUTH_SCHEME; } + break; + + case 409: +#line 1460 "parser.yxx" + { + MSG->hdr_proxy_authenticate.set_challenge(*(yyvsp[(2) - (2)].yyt_challenge)); + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_challenge)); delete (yyvsp[(2) - (2)].yyt_challenge); } + break; + + case 410: +#line 1465 "parser.yxx" + { CTXT_AUTH_SCHEME; } + break; + + case 411: +#line 1465 "parser.yxx" + { + MSG->hdr_proxy_authorization. + add_credentials(*(yyvsp[(2) - (2)].yyt_credentials)); + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_credentials)); delete (yyvsp[(2) - (2)].yyt_credentials); } + break; + + case 412: +#line 1471 "parser.yxx" + { CTXT_AUTH_SCHEME; } + break; + + case 413: +#line 1471 "parser.yxx" + { + MSG->hdr_www_authenticate.set_challenge(*(yyvsp[(2) - (2)].yyt_challenge)); + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_challenge)); delete (yyvsp[(2) - (2)].yyt_challenge); } + break; + + case 414: +#line 1476 "parser.yxx" + { CTXT_NUM; } + break; + + case 415: +#line 1476 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 416: +#line 1476 "parser.yxx" + { + MSG->hdr_rseq.set_resp_nr((yyvsp[(2) - (3)].yyt_ulong)); } + break; + + case 417: +#line 1480 "parser.yxx" + { CTXT_NUM; } + break; + + case 418: +#line 1480 "parser.yxx" + { CTXT_INITIAL; } + break; + + case 419: +#line 1480 "parser.yxx" + { + MSG->hdr_rack.set_resp_nr((yyvsp[(2) - (5)].yyt_ulong)); + MSG->hdr_rack.set_cseq_nr((yyvsp[(3) - (5)].yyt_ulong)); + MSG->hdr_rack.set_method(*(yyvsp[(5) - (5)].yyt_str)); + MEMMAN_DELETE((yyvsp[(5) - (5)].yyt_str)); delete (yyvsp[(5) - (5)].yyt_str); } + break; + + case 420: +#line 1487 "parser.yxx" + { + MSG->hdr_event.set_event_type(tolower(*(yyvsp[(1) - (2)].yyt_str))); + list::const_iterator i; + for (i = (yyvsp[(2) - (2)].yyt_params)->begin(); i != (yyvsp[(2) - (2)].yyt_params)->end(); i++) { + if (i->name == "id") { + MSG->hdr_event.set_id(i->value); + } else { + MSG->hdr_event.add_event_param(*i); + } + } + MEMMAN_DELETE((yyvsp[(1) - (2)].yyt_str)); delete (yyvsp[(1) - (2)].yyt_str); + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_params)); delete (yyvsp[(2) - (2)].yyt_params); } + break; + + case 421: +#line 1501 "parser.yxx" + { + MSG->hdr_allow_events.add_event_type(tolower(*(yyvsp[(1) - (1)].yyt_str))); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 422: +#line 1504 "parser.yxx" + { + MSG->hdr_allow_events.add_event_type(tolower(*(yyvsp[(3) - (3)].yyt_str))); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_str)); delete (yyvsp[(3) - (3)].yyt_str); } + break; + + case 423: +#line 1509 "parser.yxx" + { + MSG->hdr_subscription_state.set_substate(tolower(*(yyvsp[(1) - (2)].yyt_str))); + list::const_iterator i; + for (i = (yyvsp[(2) - (2)].yyt_params)->begin(); i != (yyvsp[(2) - (2)].yyt_params)->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((yyvsp[(1) - (2)].yyt_str)); delete (yyvsp[(1) - (2)].yyt_str); + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_params)); delete (yyvsp[(2) - (2)].yyt_params); } + break; + + case 424: +#line 1535 "parser.yxx" + { CTXT_URI_SPECIAL; } + break; + + case 425: +#line 1535 "parser.yxx" + { + MSG->hdr_refer_to.set_display((yyvsp[(2) - (3)].yyt_from_addr)->display); + MSG->hdr_refer_to.set_uri((yyvsp[(2) - (3)].yyt_from_addr)->uri); + MSG->hdr_refer_to.set_params(*(yyvsp[(3) - (3)].yyt_params)); + MEMMAN_DELETE((yyvsp[(2) - (3)].yyt_from_addr)); delete (yyvsp[(2) - (3)].yyt_from_addr); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_params)); delete (yyvsp[(3) - (3)].yyt_params); } + break; + + case 426: +#line 1543 "parser.yxx" + { CTXT_URI_SPECIAL; } + break; + + case 427: +#line 1543 "parser.yxx" + { + MSG->hdr_referred_by.set_display((yyvsp[(2) - (3)].yyt_from_addr)->display); + MSG->hdr_referred_by.set_uri((yyvsp[(2) - (3)].yyt_from_addr)->uri); + list::const_iterator i; + for (i = (yyvsp[(3) - (3)].yyt_params)->begin(); i != (yyvsp[(3) - (3)].yyt_params)->end(); i++) { + if (i->name == "cid") { + MSG->hdr_referred_by.set_cid(i->value); + } else { + MSG->hdr_referred_by.add_param(*i); + } + } + MEMMAN_DELETE((yyvsp[(2) - (3)].yyt_from_addr)); delete (yyvsp[(2) - (3)].yyt_from_addr); + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_params)); delete (yyvsp[(3) - (3)].yyt_params); } + break; + + case 428: +#line 1558 "parser.yxx" + { + string value(tolower(*(yyvsp[(1) - (2)].yyt_str))); + if (value != "true" && value != "false") { + YYERROR; + } + MSG->hdr_refer_sub.set_create_refer_sub(value == "true"); + MSG->hdr_refer_sub.set_extensions(*(yyvsp[(2) - (2)].yyt_params)); + MEMMAN_DELETE((yyvsp[(1) - (2)].yyt_str)); delete (yyvsp[(1) - (2)].yyt_str); + MEMMAN_DELETE((yyvsp[(2) - (2)].yyt_params)); delete (yyvsp[(2) - (2)].yyt_params); } + break; + + case 429: +#line 1569 "parser.yxx" + { + MSG->hdr_sip_etag.set_etag(*(yyvsp[(1) - (1)].yyt_str)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 430: +#line 1574 "parser.yxx" + { + MSG->hdr_sip_if_match.set_etag(*(yyvsp[(1) - (1)].yyt_str)); + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 431: +#line 1579 "parser.yxx" + { + bool ret = MSG->hdr_request_disposition.set_directive(*(yyvsp[(1) - (1)].yyt_str)); + if (!ret) YYERROR; + MEMMAN_DELETE((yyvsp[(1) - (1)].yyt_str)); delete (yyvsp[(1) - (1)].yyt_str); } + break; + + case 432: +#line 1583 "parser.yxx" + { + bool ret = MSG->hdr_request_disposition.set_directive(*(yyvsp[(3) - (3)].yyt_str)); + if (!ret) YYERROR; + MEMMAN_DELETE((yyvsp[(3) - (3)].yyt_str)); delete (yyvsp[(3) - (3)].yyt_str); } + break; + + +/* Line 1267 of yacc.c. */ +#line 4885 "parser.cxx" + default: break; + } + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (YY_("syntax error")); +#else + { + YYSIZE_T yysize = yysyntax_error (0, yystate, yychar); + if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM) + { + YYSIZE_T yyalloc = 2 * yysize; + if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM)) + yyalloc = YYSTACK_ALLOC_MAXIMUM; + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yyalloc); + if (yymsg) + yymsg_alloc = yyalloc; + else + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + } + } + + if (0 < yysize && yysize <= yymsg_alloc) + { + (void) yysyntax_error (yymsg, yystate, yychar); + yyerror (yymsg); + } + else + { + yyerror (YY_("syntax error")); + if (yysize != 0) + goto yyexhaustedlab; + } + } +#endif + } + + + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse look-ahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse look-ahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + /* Do not reclaim the symbols of the rule which action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (yyn != YYPACT_NINF) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + + yydestruct ("Error: popping", + yystos[yystate], yyvsp); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + if (yyn == YYFINAL) + YYACCEPT; + + *++yyvsp = yylval; + + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#ifndef yyoverflow +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEOF && yychar != YYEMPTY) + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval); + /* Do not reclaim the symbols of the rule which action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + /* Make sure YYID is used. */ + return YYID (yyresult); +} + + +#line 1588 "parser.yxx" + + +void +yyerror (const char *s) /* Called by yyparse on error */ +{ + // printf ("%s\n", s); +} + diff --git a/src/parser/parser.h b/src/parser/parser.h new file mode 100644 index 0000000..fb4ff54 --- /dev/null +++ b/src/parser/parser.h @@ -0,0 +1,252 @@ +/* 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* 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_QSTRING = 260, + T_COMMENT = 261, + T_LINE = 262, + T_URI = 263, + T_URI_WILDCARD = 264, + T_DISPLAY = 265, + T_LANG = 266, + T_WORD = 267, + T_WKDAY = 268, + T_MONTH = 269, + T_GMT = 270, + T_SIP = 271, + T_METHOD = 272, + T_AUTH_DIGEST = 273, + T_AUTH_OTHER = 274, + T_IPV6ADDR = 275, + T_PARAMVAL = 276, + T_HDR_ACCEPT = 277, + T_HDR_ACCEPT_ENCODING = 278, + T_HDR_ACCEPT_LANGUAGE = 279, + T_HDR_ALERT_INFO = 280, + T_HDR_ALLOW = 281, + T_HDR_ALLOW_EVENTS = 282, + T_HDR_AUTHENTICATION_INFO = 283, + T_HDR_AUTHORIZATION = 284, + T_HDR_CALL_ID = 285, + T_HDR_CALL_INFO = 286, + T_HDR_CONTACT = 287, + T_HDR_CONTENT_DISP = 288, + T_HDR_CONTENT_ENCODING = 289, + T_HDR_CONTENT_LANGUAGE = 290, + T_HDR_CONTENT_LENGTH = 291, + T_HDR_CONTENT_TYPE = 292, + T_HDR_CSEQ = 293, + T_HDR_DATE = 294, + T_HDR_ERROR_INFO = 295, + T_HDR_EVENT = 296, + T_HDR_EXPIRES = 297, + T_HDR_FROM = 298, + T_HDR_IN_REPLY_TO = 299, + T_HDR_MAX_FORWARDS = 300, + T_HDR_MIN_EXPIRES = 301, + T_HDR_MIME_VERSION = 302, + T_HDR_ORGANIZATION = 303, + T_HDR_P_ASSERTED_IDENTITY = 304, + T_HDR_P_PREFERRED_IDENTITY = 305, + T_HDR_PRIORITY = 306, + T_HDR_PRIVACY = 307, + T_HDR_PROXY_AUTHENTICATE = 308, + T_HDR_PROXY_AUTHORIZATION = 309, + T_HDR_PROXY_REQUIRE = 310, + T_HDR_RACK = 311, + T_HDR_RECORD_ROUTE = 312, + T_HDR_SERVICE_ROUTE = 313, + T_HDR_REFER_SUB = 314, + T_HDR_REFER_TO = 315, + T_HDR_REFERRED_BY = 316, + T_HDR_REPLACES = 317, + T_HDR_REPLY_TO = 318, + T_HDR_REQUIRE = 319, + T_HDR_REQUEST_DISPOSITION = 320, + T_HDR_RETRY_AFTER = 321, + T_HDR_ROUTE = 322, + T_HDR_RSEQ = 323, + T_HDR_SERVER = 324, + T_HDR_SIP_ETAG = 325, + T_HDR_SIP_IF_MATCH = 326, + T_HDR_SUBJECT = 327, + T_HDR_SUBSCRIPTION_STATE = 328, + T_HDR_SUPPORTED = 329, + T_HDR_TIMESTAMP = 330, + T_HDR_TO = 331, + T_HDR_UNSUPPORTED = 332, + T_HDR_USER_AGENT = 333, + T_HDR_VIA = 334, + T_HDR_WARNING = 335, + T_HDR_WWW_AUTHENTICATE = 336, + T_HDR_UNKNOWN = 337, + T_CRLF = 338, + T_ERROR = 339, + T_NULL = 340 + }; +#endif +/* Tokens. */ +#define T_NUM 258 +#define T_TOKEN 259 +#define T_QSTRING 260 +#define T_COMMENT 261 +#define T_LINE 262 +#define T_URI 263 +#define T_URI_WILDCARD 264 +#define T_DISPLAY 265 +#define T_LANG 266 +#define T_WORD 267 +#define T_WKDAY 268 +#define T_MONTH 269 +#define T_GMT 270 +#define T_SIP 271 +#define T_METHOD 272 +#define T_AUTH_DIGEST 273 +#define T_AUTH_OTHER 274 +#define T_IPV6ADDR 275 +#define T_PARAMVAL 276 +#define T_HDR_ACCEPT 277 +#define T_HDR_ACCEPT_ENCODING 278 +#define T_HDR_ACCEPT_LANGUAGE 279 +#define T_HDR_ALERT_INFO 280 +#define T_HDR_ALLOW 281 +#define T_HDR_ALLOW_EVENTS 282 +#define T_HDR_AUTHENTICATION_INFO 283 +#define T_HDR_AUTHORIZATION 284 +#define T_HDR_CALL_ID 285 +#define T_HDR_CALL_INFO 286 +#define T_HDR_CONTACT 287 +#define T_HDR_CONTENT_DISP 288 +#define T_HDR_CONTENT_ENCODING 289 +#define T_HDR_CONTENT_LANGUAGE 290 +#define T_HDR_CONTENT_LENGTH 291 +#define T_HDR_CONTENT_TYPE 292 +#define T_HDR_CSEQ 293 +#define T_HDR_DATE 294 +#define T_HDR_ERROR_INFO 295 +#define T_HDR_EVENT 296 +#define T_HDR_EXPIRES 297 +#define T_HDR_FROM 298 +#define T_HDR_IN_REPLY_TO 299 +#define T_HDR_MAX_FORWARDS 300 +#define T_HDR_MIN_EXPIRES 301 +#define T_HDR_MIME_VERSION 302 +#define T_HDR_ORGANIZATION 303 +#define T_HDR_P_ASSERTED_IDENTITY 304 +#define T_HDR_P_PREFERRED_IDENTITY 305 +#define T_HDR_PRIORITY 306 +#define T_HDR_PRIVACY 307 +#define T_HDR_PROXY_AUTHENTICATE 308 +#define T_HDR_PROXY_AUTHORIZATION 309 +#define T_HDR_PROXY_REQUIRE 310 +#define T_HDR_RACK 311 +#define T_HDR_RECORD_ROUTE 312 +#define T_HDR_SERVICE_ROUTE 313 +#define T_HDR_REFER_SUB 314 +#define T_HDR_REFER_TO 315 +#define T_HDR_REFERRED_BY 316 +#define T_HDR_REPLACES 317 +#define T_HDR_REPLY_TO 318 +#define T_HDR_REQUIRE 319 +#define T_HDR_REQUEST_DISPOSITION 320 +#define T_HDR_RETRY_AFTER 321 +#define T_HDR_ROUTE 322 +#define T_HDR_RSEQ 323 +#define T_HDR_SERVER 324 +#define T_HDR_SIP_ETAG 325 +#define T_HDR_SIP_IF_MATCH 326 +#define T_HDR_SUBJECT 327 +#define T_HDR_SUBSCRIPTION_STATE 328 +#define T_HDR_SUPPORTED 329 +#define T_HDR_TIMESTAMP 330 +#define T_HDR_TO 331 +#define T_HDR_UNSUPPORTED 332 +#define T_HDR_USER_AGENT 333 +#define T_HDR_VIA 334 +#define T_HDR_WARNING 335 +#define T_HDR_WWW_AUTHENTICATE 336 +#define T_HDR_UNKNOWN 337 +#define T_CRLF 338 +#define T_ERROR 339 +#define T_NULL 340 + + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE +#line 49 "parser.yxx" +{ + 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; +} +/* Line 1529 of yacc.c. */ +#line 245 "parser.h" + YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + +extern YYSTYPE yylval; + diff --git a/src/parser/parser.yxx b/src/parser/parser.yxx new file mode 100644 index 0000000..6f53e3c --- /dev/null +++ b/src/parser/parser.yxx @@ -0,0 +1,1594 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +%{ +#include +#include +#include +#include "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); +} diff --git a/src/parser/request.cpp b/src/parser/request.cpp new file mode 100644 index 0000000..1e678d4 --- /dev/null +++ b/src/parser/request.cpp @@ -0,0 +1,769 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "request.h" +#include "util.h" +#include "parse_ctrl.h" +#include "protocol.h" +#include "milenage.h" +#include "audits/memman.h" +#include +#include + +using namespace ost; + +// 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 string &username, const string &passwd, uint8 *op, uint8 *amf, + unsigned long nc, + const string &cnonce, const string &qop, string &resp, + 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]; + + string res_str = 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 string &username, const string &passwd, unsigned long nc, + const string &cnonce, const string &qop, string &resp, + string &fail_reason) const +{ + 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) { + MD5Digest MD5body; + MD5body << body->encode(); + ostringstream os; + os << MD5body; + A2 += os.str(); + } else { + MD5Digest MD5body; + MD5body << ""; + ostringstream os; + os << MD5body; + A2 += os.str(); + } + } + + // RFC 2716 3.2.2.1 + // Caculate digest + MD5Digest MD5A1; + MD5Digest MD5A2; + ostringstream HA1; + ostringstream HA2; + + MD5A1 << A1; + MD5A2 << A2; + HA1 << MD5A1; + HA2 << MD5A2; + + string x; + + if (cmp_nocase(qop, QOP_AUTH) == 0 || cmp_nocase(qop, QOP_AUTH_INT) == 0) { + x = HA1.str() + ":"; + x += dchlg.nonce + ":"; + x += int2str(nc, "%08x") + ":"; + x += cnonce + ":"; + x += qop + ":"; + x += HA2.str(); + } else { + x = HA1.str() + ":"; + x += dchlg.nonce + ":"; + x += HA2.str(); + } + + MD5Digest digest; + digest << x; + ostringstream dresp; + dresp << digest; + + resp = dresp.str(); + + return true; +} + +bool t_request::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 +{ + // 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; + + 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; + 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 string &s) { + method = str2method(s); + if (method == METHOD_UNKNOWN) { + unknown_method = s; + } +} + +string t_request::encode(bool add_content_length) { + 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) { + 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, 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, 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 string &username, const string &passwd, unsigned long nc, + const string &cnonce, t_credentials &cr, 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 string &username, const string &passwd, unsigned long nc, + const string &cnonce, t_credentials &cr, 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; +} diff --git a/src/parser/request.h b/src/parser/request.h new file mode 100644 index 0000000..a2fe879 --- /dev/null +++ b/src/parser/request.h @@ -0,0 +1,218 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 succesful 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 succesful 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 succesful 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 succesful 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 succesful 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 diff --git a/src/parser/response.cpp b/src/parser/response.cpp new file mode 100755 index 0000000..83f47eb --- /dev/null +++ b/src/parser/response.cpp @@ -0,0 +1,237 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +#include "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); + } +} diff --git a/src/parser/response.h b/src/parser/response.h new file mode 100644 index 0000000..a2f87c6 --- /dev/null +++ b/src/parser/response.h @@ -0,0 +1,214 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/rijndael.cpp b/src/parser/rijndael.cpp new file mode 100644 index 0000000..0ac000b --- /dev/null +++ b/src/parser/rijndael.cpp @@ -0,0 +1,440 @@ +/*------------------------------------------------------------------- + * 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]); +} diff --git a/src/parser/rijndael.h b/src/parser/rijndael.h new file mode 100644 index 0000000..4e728a5 --- /dev/null +++ b/src/parser/rijndael.h @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------- + * 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 diff --git a/src/parser/route.cpp b/src/parser/route.cpp new file mode 100644 index 0000000..cf57098 --- /dev/null +++ b/src/parser/route.cpp @@ -0,0 +1,48 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/parser/route.h b/src/parser/route.h new file mode 100644 index 0000000..3e07b47 --- /dev/null +++ b/src/parser/route.h @@ -0,0 +1,41 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/scanner.cxx b/src/parser/scanner.cxx new file mode 100644 index 0000000..c1c0f23 --- /dev/null +++ b/src/parser/scanner.cxx @@ -0,0 +1,3485 @@ + +#line 3 "scanner.cxx" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 5 +#define YY_FLEX_SUBMINOR_VERSION 33 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; +#endif /* ! C99 */ + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#endif /* ! FLEXINT_H */ + +#ifdef __cplusplus + +/* The "const" storage-class-modifier is valid. */ +#define YY_USE_CONST + +#else /* ! __cplusplus */ + +#if __STDC__ + +#define YY_USE_CONST + +#endif /* __STDC__ */ +#endif /* ! __cplusplus */ + +#ifdef YY_USE_CONST +#define yyconst const +#else +#define yyconst +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an unsigned + * integer for use as an array index. If the signed char is negative, + * we want to instead treat it as an 8-bit unsigned char, hence the + * double cast. + */ +#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN (yy_start) = 1 + 2 * + +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START (((yy_start) - 1) / 2) +#define YYSTATE YY_START + +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) + +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart(yyin ) + +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#define YY_BUF_SIZE 16384 +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +extern int yyleng; + +extern FILE *yyin, *yyout; + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = (yy_hold_char); \ + YY_RESTORE_YY_MORE_OFFSET \ + (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) + +#define unput(c) yyunput( c, (yytext_ptr) ) + +/* The following is because we cannot portably get our hands on size_t + * (without autoconf's help, which isn't available because we want + * flex-generated scanners to compile on their own). + */ + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef unsigned int yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + yy_size_t yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* Stack of input buffers. */ +static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */ +static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */ +static YY_BUFFER_STATE * yy_buffer_stack = 0; /**< Stack as an array. */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \ + ? (yy_buffer_stack)[(yy_buffer_stack_top)] \ + : NULL) + +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)] + +/* yy_hold_char holds the character lost when yytext is formed. */ +static char yy_hold_char; +static int yy_n_chars; /* number of characters read into yy_ch_buf */ +int yyleng; + +/* Points to current character in buffer. */ +static char *yy_c_buf_p = (char *) 0; +static int yy_init = 0; /* whether we need to initialize */ +static int yy_start = 0; /* start state number */ + +/* Flag which is used to allow yywrap()'s to do buffer switches + * instead of setting up a fresh yyin. A bit of a hack ... + */ +static int yy_did_buffer_switch_on_eof; + +void yyrestart (FILE *input_file ); +void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ); +YY_BUFFER_STATE yy_create_buffer (FILE *file,int size ); +void yy_delete_buffer (YY_BUFFER_STATE b ); +void yy_flush_buffer (YY_BUFFER_STATE b ); +void yypush_buffer_state (YY_BUFFER_STATE new_buffer ); +void yypop_buffer_state (void ); + +static void yyensure_buffer_stack (void ); +static void yy_load_buffer_state (void ); +static void yy_init_buffer (YY_BUFFER_STATE b,FILE *file ); + +#define YY_FLUSH_BUFFER yy_flush_buffer(YY_CURRENT_BUFFER ) + +YY_BUFFER_STATE yy_scan_buffer (char *base,yy_size_t size ); +YY_BUFFER_STATE yy_scan_string (yyconst char *yy_str ); +YY_BUFFER_STATE yy_scan_bytes (yyconst char *bytes,int len ); + +void *yyalloc (yy_size_t ); +void *yyrealloc (void *,yy_size_t ); +void yyfree (void * ); + +#define yy_new_buffer yy_create_buffer + +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer(yyin,YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } + +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer(yyin,YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } + +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +#define yywrap(n) 1 +#define YY_SKIP_YYWRAP + +typedef unsigned char YY_CHAR; + +FILE *yyin = (FILE *) 0, *yyout = (FILE *) 0; + +typedef int yy_state_type; + +extern int yylineno; +extern char *yytext; +#define yytext_ptr yytext + +static yy_state_type yy_get_previous_state (void ); +static yy_state_type yy_try_NUL_trans (yy_state_type current_state ); +static int yy_get_next_buffer (void ); +static void yy_fatal_error (yyconst char msg[] ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + (yytext_ptr) = yy_bp; \ + (yytext_ptr) -= (yy_more_len); \ + yyleng = (size_t) (yy_cp - (yytext_ptr)); \ + (yy_hold_char) = *yy_cp; \ + *yy_cp = '\0'; \ + (yy_c_buf_p) = yy_cp; + +#define YY_NUM_RULES 163 +#define YY_END_OF_BUFFER 164 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static yyconst flex_int16_t yy_acclist[1388] = + { 0, + 164, 67, 163, 66, 67, 163, 65, 163, 67, 163, + 62, 67, 163, 63, 67, 163, 61, 62, 67, 163, + 61, 62, 67, 163, 40, 61, 62, 67, 163, 16, + 61, 62, 67, 163, 44, 61, 62, 67, 163, 13, + 61, 62, 67, 163, 22, 61, 62, 67, 163, 9, + 61, 62, 67, 163, 53, 61, 62, 67, 163, 15, + 61, 62, 67, 163, 11, 61, 62, 67, 163, 20, + 61, 62, 67, 163, 61, 62, 67, 163, 39, 61, + 62, 67, 163, 51, 61, 62, 67, 163, 55, 61, + 62, 67, 163, 6, 61, 62, 67, 163, 58, 61, + + 62, 67, 163, 61, 62, 67, 163, 72, 74, 163, + 16454, 73, 74, 163, 75, 163, 74, 163, 72, 74, + 163,16454, 68, 74, 163,16454, 71, 72, 74, 163, + 16454, 79, 81, 163, 80, 81, 163, 82, 163, 81, + 163, 79, 81, 163, 76, 81, 163, 78, 79, 81, + 163, 87, 163, 86, 163, 87, 163, 85, 87, 163, + 83, 87, 163, 98, 163, 97, 98, 163, 100, 163, + 98, 163, 96, 98, 163, 103, 163, 102, 103, 163, + 105, 163, 103, 163, 101, 103, 163, 108, 163, 107, + 108, 163, 110, 163, 108, 163, 106, 108, 163, 133, + + 163, 132, 133, 163, 135, 163, 133, 163, 131, 133, + 163, 133, 163, 133, 163, 133, 163, 133, 163, 133, + 163, 133, 163, 133, 163, 133, 163, 133, 163, 133, + 163, 133, 163, 136, 163, 138, 163, 139, 163, 94, + 163, 93, 163, 94, 163, 91, 94, 163, 92, 94, + 163, 88, 94, 163, 143, 163, 142, 143, 163, 145, + 163, 143, 163, 141, 143, 163, 141, 143, 163, 149, + 163, 148, 149, 163, 151, 163, 149, 163, 147, 149, + 163, 147, 149, 163, 163, 95, 163, 154, 163, 153, + 154, 163, 156, 163, 154, 163, 152, 154, 163, 160, + + 163, 159, 160, 163, 162, 163, 160, 163, 157, 160, + 163, 158, 160, 163, 64, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 55, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 72,16454,16454, 8262, 72,16454, 69, 79, 79, + 77, 86, 85, 84, 99, 96, 104, 101, 109, 106, + + 134, 131, 136, 137, 93, 91, 92, 90, 89, 144, + 141, 141, 150, 147, 147, 155, 152, 161, 157, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 58, 61, 62, 61, 62, 61, 62, 96, 96, + + 121, 125, 129, 119, 115, 130, 118, 124, 123, 120, + 122, 111, 128, 127, 116, 126, 117, 114, 112, 113, + 140, 141, 147, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 17, 61, 62, 18, 61, + 62, 61, 62, 61, 62, 61, 62, 22, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 35, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 47, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + + 61, 62, 61, 62, 61, 62, 61, 62, 96, 96, + 147, 61, 62, 61, 62, 5, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 20, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 46, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 96, 96, 147, 1, + + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 48, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 96, 96, 146, 147, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 9, 61, 62, 61, 62, + + 11, 61, 62, 61, 62, 61, 62, 21, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 31, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 43, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 51, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 59, 61, 62, 61, 62, 96, 96, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 30, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 39, 61, 62, 61, 62, 41, 61, 62, 42, + 61, 62, 61, 62, 61, 62, 61, 62, 49, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 96, 96, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 10, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + + 61, 62, 38, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 53, 61, 62, + 54, 61, 62, 61, 62, 61, 62, 61, 62, 96, + 61, 62, 61, 62, 4, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 19, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 57, 61, 62, 61, 62, 96, 61, 62, 61, 62, + + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 23, 61, 62, 61, + 62, 61, 62, 25, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 40, 61, 62, 61, 62, 45, 61, 62, 61, 62, + 61, 62, 61, 62, 56, 61, 62, 61, 62, 61, + 62, 61, 62, 6, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 16, 61, + 62, 24, 61, 62, 26, 61, 62, 27, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + + 36, 61, 62, 61, 62, 61, 62, 50, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 8, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 34, + 61, 62, 61, 62, 37, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 15, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 2, 61, 62, 3, 61, 62, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 61, 62, 13, 61, 62, 14, 61, 62, 61, 62, + 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, + 60, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, + 62, 61, 62, 61, 62, 61, 62, 32, 61, 62, + 61, 62, 61, 62, 52, 61, 62, 7, 61, 62, + 12, 61, 62, 28, 61, 62, 61, 62, 33, 61, + 62, 44, 61, 62, 29, 61, 62 + } ; + +static yyconst flex_int16_t yy_accept[715] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 4, 7, 9, 11, 14, 17, 21, 25, + 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, + 79, 84, 89, 94, 99, 104, 108, 112, 115, 117, + 119, 123, 127, 132, 135, 138, 140, 142, 145, 148, + 152, 154, 156, 158, 161, 164, 166, 169, 171, 173, + 176, 178, 181, 183, 185, 188, 190, 193, 195, 197, + 200, 202, 205, 207, 209, 212, 214, 216, 218, 220, + + 222, 224, 226, 228, 230, 232, 234, 236, 238, 240, + 242, 244, 246, 249, 252, 255, 257, 260, 262, 264, + 267, 270, 272, 275, 277, 279, 282, 285, 286, 288, + 290, 293, 295, 297, 300, 302, 305, 307, 309, 312, + 315, 316, 317, 319, 321, 323, 325, 327, 329, 331, + 333, 335, 337, 339, 341, 343, 345, 347, 349, 351, + 353, 355, 357, 359, 361, 363, 365, 367, 369, 372, + 374, 376, 378, 380, 382, 384, 384, 385, 386, 386, + 388, 389, 390, 390, 391, 392, 392, 393, 393, 394, + 394, 395, 396, 396, 397, 398, 399, 400, 401, 402, + + 403, 403, 403, 403, 403, 403, 403, 403, 403, 403, + 403, 403, 403, 403, 403, 403, 403, 403, 403, 404, + 405, 405, 406, 406, 407, 408, 408, 409, 410, 411, + 412, 413, 414, 415, 416, 417, 418, 419, 420, 422, + 424, 426, 428, 430, 432, 434, 436, 438, 440, 442, + 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, + 464, 466, 468, 470, 472, 474, 476, 478, 480, 482, + 484, 486, 488, 490, 492, 495, 497, 499, 499, 500, + 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, + 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, + + 521, 523, 524, 526, 528, 530, 532, 534, 536, 539, + 542, 544, 546, 548, 551, 553, 555, 557, 559, 561, + 563, 565, 567, 569, 571, 574, 576, 578, 580, 582, + 584, 586, 589, 591, 593, 595, 597, 599, 601, 603, + 605, 607, 609, 610, 611, 612, 614, 616, 619, 621, + 623, 625, 627, 629, 631, 634, 636, 638, 640, 642, + 644, 646, 648, 650, 652, 654, 656, 658, 660, 662, + 664, 666, 668, 670, 673, 675, 677, 679, 681, 683, + 685, 687, 689, 691, 693, 695, 697, 698, 699, 700, + 703, 705, 707, 709, 711, 713, 715, 717, 719, 721, + + 723, 725, 727, 729, 731, 733, 735, 737, 739, 741, + 743, 745, 747, 749, 751, 753, 755, 757, 760, 762, + 764, 766, 768, 770, 772, 774, 776, 778, 780, 782, + 783, 784, 786, 788, 790, 792, 794, 796, 799, 801, + 804, 806, 808, 811, 813, 815, 817, 819, 821, 823, + 825, 827, 830, 832, 834, 836, 838, 840, 842, 844, + 846, 848, 851, 853, 855, 857, 859, 862, 864, 866, + 868, 870, 872, 875, 877, 878, 879, 881, 883, 885, + 887, 889, 891, 893, 895, 897, 899, 901, 903, 905, + 907, 909, 911, 914, 916, 918, 920, 922, 925, 927, + + 930, 933, 935, 937, 939, 942, 944, 946, 948, 950, + 952, 954, 956, 957, 958, 960, 962, 964, 966, 968, + 970, 973, 975, 977, 979, 981, 983, 985, 987, 989, + 991, 993, 995, 997, 999, 1001, 1003, 1006, 1008, 1010, + 1012, 1014, 1016, 1018, 1021, 1024, 1026, 1028, 1030, 1031, + 1033, 1035, 1038, 1040, 1042, 1044, 1046, 1048, 1050, 1052, + 1054, 1057, 1059, 1061, 1063, 1065, 1067, 1069, 1071, 1073, + 1075, 1077, 1079, 1081, 1083, 1085, 1087, 1089, 1091, 1094, + 1096, 1097, 1099, 1101, 1103, 1105, 1107, 1109, 1111, 1113, + 1115, 1117, 1120, 1122, 1124, 1127, 1129, 1131, 1133, 1135, + + 1137, 1139, 1141, 1144, 1146, 1149, 1151, 1153, 1155, 1158, + 1160, 1162, 1164, 1167, 1169, 1171, 1173, 1175, 1177, 1179, + 1182, 1185, 1188, 1191, 1193, 1195, 1197, 1199, 1201, 1204, + 1206, 1208, 1211, 1213, 1215, 1217, 1219, 1221, 1224, 1226, + 1228, 1230, 1232, 1234, 1236, 1238, 1240, 1243, 1245, 1248, + 1250, 1252, 1254, 1256, 1258, 1260, 1262, 1264, 1267, 1269, + 1271, 1273, 1275, 1277, 1279, 1281, 1284, 1287, 1289, 1291, + 1293, 1295, 1297, 1299, 1301, 1303, 1305, 1307, 1309, 1311, + 1313, 1316, 1319, 1321, 1323, 1325, 1327, 1329, 1331, 1334, + 1336, 1338, 1340, 1342, 1344, 1346, 1348, 1350, 1352, 1354, + + 1356, 1358, 1361, 1363, 1365, 1368, 1371, 1374, 1377, 1379, + 1382, 1385, 1388, 1388 + } ; + +static yyconst flex_int32_t yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 5, 6, 1, 1, 5, 1, 5, 7, + 8, 9, 5, 1, 10, 11, 12, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 14, 15, 16, + 1, 17, 12, 1, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 18, 19, 18, 1, 5, 5, 20, 21, 22, 23, + + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 12, 1, 12, 5, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static yyconst flex_int32_t yy_meta[46] = + { 0, + 1, 2, 3, 3, 4, 5, 6, 6, 4, 7, + 8, 9, 8, 10, 11, 12, 13, 14, 15, 16, + 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17 + } ; + +static yyconst flex_int16_t yy_base[761] = + { 0, + 0, 19, 64, 83, 102, 121, 128, 138, 157, 0, + 202, 206, 209, 212, 224, 265, 6, 71, 216, 226, + 307, 0, 352, 375, 1453, 1452, 397, 421, 446, 465, + 1459, 1462, 1462, 1462, 1455, 0, 1462, 0, 220, 0, + 198, 1437, 108, 1419, 1422, 0, 0, 49, 1417, 78, + 241, 215, 79, 110, 1425, 228, 270, 1462, 1462, 1462, + 484, 229, 503, 0, 1462, 1462, 1462, 522, 1462, 541, + 277, 1462, 1449, 1462, 1445, 1462, 1462, 1462, 1447, 1439, + 1462, 1462, 1462, 1445, 0, 1462, 1462, 1462, 1444, 1433, + 1462, 1462, 1462, 1442, 1431, 115, 1419, 217, 1410, 106, + + 218, 1407, 1418, 253, 244, 1415, 0, 1462, 1435, 543, + 1462, 1434, 1462, 1462, 86, 1462, 1462, 1462, 1433, 0, + 1407, 1462, 1462, 1462, 1431, 0, 1405, 1462, 1462, 1462, + 1462, 1462, 1429, 0, 1462, 1462, 1462, 1428, 0, 1462, + 1462, 0, 0, 1408, 258, 1390, 1397, 1394, 1402, 1386, + 1387, 1399, 1387, 1387, 1410, 1376, 120, 1392, 239, 258, + 1395, 391, 1376, 1391, 1377, 1378, 351, 1380, 0, 1373, + 1386, 1389, 1371, 1365, 378, 371, 395, 1462, 412, 0, + 1462, 0, 1390, 0, 1462, 560, 1462, 1402, 1462, 1398, + 1462, 1462, 0, 1393, 1462, 0, 1462, 1389, 1462, 1388, + + 1363, 1373, 1376, 1376, 1368, 1356, 1361, 123, 258, 1360, + 1351, 1352, 1351, 1354, 1355, 1347, 1362, 1362, 0, 1462, + 564, 1462, 1381, 1462, 1462, 293, 1462, 1462, 1462, 0, + 1348, 1462, 0, 1356, 1462, 0, 1462, 0, 1357, 1343, + 1345, 1351, 1346, 1337, 1339, 1350, 1339, 1339, 1343, 1338, + 1332, 1358, 1343, 1356, 1345, 1326, 1326, 362, 1319, 1331, + 1326, 1335, 1327, 1317, 1319, 1316, 1318, 1312, 1342, 336, + 1316, 1326, 1309, 1311, 0, 1314, 1336, 289, 1335, 1334, + 1462, 1462, 1462, 1462, 1462, 1462, 1462, 1462, 1462, 1462, + 1462, 1462, 1462, 1462, 1462, 1462, 1462, 1462, 1462, 1462, + + 0, 1319, 1307, 1302, 1298, 381, 1329, 337, 0, 0, + 1301, 1298, 1299, 0, 1311, 1309, 1323, 1308, 1298, 1292, + 1305, 1291, 1307, 1282, 0, 1288, 1287, 220, 378, 1279, + 1298, 0, 409, 412, 1297, 1298, 1285, 1280, 1282, 1306, + 1287, 1294, 1303, 1302, 1273, 1271, 1299, 1298, 1274, 1269, + 1277, 1282, 1270, 1292, 0, 1277, 1265, 1265, 1257, 1254, + 1268, 1271, 1269, 1265, 1270, 1281, 1267, 394, 1267, 1278, + 1249, 1249, 1275, 0, 1247, 1261, 1243, 1256, 1258, 1242, + 1241, 1238, 1241, 1255, 1241, 1233, 1262, 1261, 1231, 1259, + 1240, 1243, 1227, 1237, 437, 1225, 1224, 1234, 1223, 1229, + + 1222, 1234, 1222, 1211, 1218, 1230, 1214, 1208, 418, 1241, + 324, 1226, 1225, 1209, 1208, 1222, 1225, 0, 1220, 1223, + 1232, 1200, 1210, 1198, 1216, 1201, 1208, 1207, 1193, 1218, + 1217, 0, 433, 1193, 1184, 1196, 1178, 0, 1197, 0, + 1211, 1184, 0, 1172, 1173, 1177, 1185, 1192, 1172, 1173, + 1162, 0, 1165, 1180, 1166, 1162, 1167, 1177, 1161, 1161, + 1184, 0, 1168, 1182, 1165, 1158, 0, 1154, 1164, 1152, + 1146, 1158, 0, 1154, 1170, 1169, 1145, 1157, 1148, 1148, + 1149, 1150, 1135, 546, 1143, 1157, 1146, 1124, 1124, 1121, + 1135, 1121, 0, 1118, 1120, 1121, 1130, 0, 1140, 0, + + 0, 1126, 1109, 1110, 0, 1126, 1106, 1121, 1105, 1100, + 1105, 1113, 1126, 1125, 1112, 1100, 1095, 1095, 1107, 1087, + 0, 1097, 1091, 454, 1079, 1088, 1079, 1080, 1088, 1091, + 1086, 1090, 1088, 1084, 1067, 1066, 0, 1084, 1076, 1079, + 1068, 1062, 1072, 0, 0, 1072, 1056, 1061, 1082, 1057, + 1064, 0, 1048, 1047, 1056, 1042, 1057, 1043, 1042, 1039, + 0, 1037, 1047, 1034, 1026, 1028, 1051, 1037, 455, 1030, + 1016, 1010, 1013, 1013, 1009, 1026, 1013, 1022, 0, 1002, + 1030, 1016, 998, 999, 1008, 1001, 997, 997, 1001, 992, + 957, 0, 934, 929, 0, 928, 932, 949, 925, 920, + + 919, 931, 0, 916, 0, 902, 888, 880, 0, 879, + 877, 878, 0, 863, 863, 856, 865, 847, 841, 0, + 0, 0, 0, 848, 826, 807, 809, 808, 0, 797, + 806, 0, 819, 806, 794, 800, 790, 0, 784, 790, + 797, 789, 791, 791, 785, 767, 0, 771, 0, 770, + 564, 556, 557, 570, 550, 543, 549, 0, 541, 549, + 543, 541, 530, 517, 515, 0, 0, 524, 506, 518, + 515, 498, 502, 513, 493, 492, 508, 503, 493, 497, + 0, 0, 488, 475, 474, 484, 480, 468, 0, 481, + 463, 456, 466, 469, 449, 425, 432, 417, 406, 365, + + 368, 0, 352, 79, 0, 0, 0, 0, 52, 0, + 0, 0, 1462, 585, 602, 619, 636, 653, 670, 687, + 704, 721, 738, 755, 772, 789, 803, 817, 834, 851, + 868, 885, 902, 919, 936, 947, 961, 978, 995, 1012, + 459, 1026, 1036, 1049, 1065, 1081, 471, 1092, 1103, 1114, + 1125, 1136, 1147, 1158, 1169, 1180, 1191, 1202, 1213, 1224 + } ; + +static yyconst flex_int16_t yy_def[761] = + { 0, + 714, 713, 715, 715, 716, 716, 717, 717, 713, 9, + 718, 718, 719, 719, 720, 720, 721, 721, 722, 722, + 713, 21, 723, 723, 724, 724, 725, 725, 726, 726, + 713, 713, 713, 713, 713, 727, 713, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 729, 713, 713, 713, + 730, 731, 730, 732, 713, 713, 713, 733, 713, 733, + 734, 713, 735, 713, 713, 713, 713, 713, 713, 736, + 713, 713, 713, 713, 737, 713, 713, 713, 713, 713, + 713, 713, 713, 713, 713, 713, 713, 713, 713, 713, + + 713, 713, 713, 713, 713, 713, 738, 713, 713, 739, + 713, 740, 713, 713, 713, 713, 713, 713, 713, 741, + 741, 713, 713, 713, 713, 742, 742, 713, 713, 713, + 713, 713, 713, 743, 713, 713, 713, 713, 744, 713, + 713, 727, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 729, 713, 731, 713, 745, 63, + 713, 732, 746, 70, 713, 734, 713, 735, 713, 713, + 713, 713, 747, 748, 713, 737, 713, 713, 713, 713, + + 713, 713, 713, 713, 713, 713, 713, 713, 713, 713, + 713, 713, 713, 713, 713, 713, 713, 713, 738, 713, + 739, 713, 740, 713, 713, 713, 713, 713, 713, 741, + 741, 713, 742, 742, 713, 743, 713, 744, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 745, 749, 750, + 713, 713, 713, 713, 713, 713, 713, 713, 713, 713, + 713, 713, 713, 713, 713, 713, 713, 713, 713, 713, + + 741, 742, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 751, 752, 742, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 753, 754, 742, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 755, + 756, 742, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 757, 758, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 759, 713, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 760, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 713, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + + 728, 728, 728, 728, 728, 728, 728, 728, 728, 728, + 728, 728, 0, 713, 713, 713, 713, 713, 713, 713, + 713, 713, 713, 713, 713, 713, 713, 713, 713, 713, + 713, 713, 713, 713, 713, 713, 713, 713, 713, 713, + 713, 713, 713, 713, 713, 713, 713, 713, 713, 713, + 713, 713, 713, 713, 713, 713, 713, 713, 713, 713 + } ; + +static yyconst flex_int16_t yy_nxt[1508] = + { 0, + 32, 33, 34, 35, 713, 37, 32, 32, 108, 109, + 713, 32, 713, 32, 32, 32, 32, 32, 32, 32, + 33, 34, 35, 38, 37, 32, 32, 38, 38, 38, + 32, 38, 32, 32, 32, 32, 32, 32, 39, 40, + 41, 42, 43, 44, 38, 38, 45, 38, 46, 47, + 48, 38, 49, 50, 38, 51, 52, 53, 54, 55, + 56, 38, 38, 38, 57, 58, 59, 60, 156, 62, + 57, 57, 63, 108, 109, 57, 157, 57, 57, 60, + 60, 57, 57, 57, 58, 59, 60, 159, 62, 57, + 57, 63, 227, 228, 57, 712, 57, 57, 60, 60, + + 57, 57, 64, 65, 66, 67, 168, 69, 64, 64, + 70, 711, 169, 64, 160, 64, 67, 67, 67, 64, + 64, 64, 65, 66, 67, 207, 69, 64, 64, 70, + 72, 73, 64, 74, 64, 67, 67, 67, 64, 64, + 72, 73, 170, 74, 151, 208, 75, 171, 152, 201, + 153, 253, 254, 288, 202, 289, 75, 76, 77, 78, + 79, 76, 76, 76, 76, 76, 76, 76, 76, 76, + 76, 76, 76, 76, 76, 76, 80, 80, 80, 80, + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, + + 80, 80, 81, 82, 83, 84, 81, 82, 83, 84, + 87, 88, 89, 87, 88, 89, 81, 147, 111, 112, + 81, 90, 113, 114, 90, 92, 93, 94, 111, 112, + 176, 148, 113, 114, 115, 149, 95, 209, 165, 369, + 204, 144, 166, 96, 115, 178, 97, 173, 98, 99, + 145, 210, 100, 205, 167, 101, 102, 103, 256, 146, + 161, 104, 105, 370, 162, 106, 92, 93, 94, 174, + 216, 176, 213, 257, 163, 177, 214, 95, 164, 187, + 188, 240, 189, 217, 96, 258, 178, 97, 241, 98, + 99, 259, 215, 100, 290, 190, 101, 102, 103, 227, + + 228, 291, 104, 105, 181, 713, 106, 116, 117, 118, + 119, 116, 116, 116, 116, 116, 116, 116, 116, 116, + 116, 116, 116, 116, 116, 116, 120, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 121, 120, 120, 120, 120, 120, + 120, 120, 122, 123, 124, 125, 352, 122, 122, 122, + 353, 456, 457, 122, 335, 122, 122, 122, 122, 122, + 122, 270, 176, 336, 127, 122, 123, 124, 125, 176, + 122, 122, 122, 177, 710, 271, 122, 178, 122, 122, + 122, 122, 122, 122, 178, 322, 176, 127, 131, 132, + + 133, 371, 323, 411, 349, 372, 709, 134, 708, 134, + 134, 178, 261, 179, 350, 262, 134, 134, 134, 134, + 134, 134, 131, 132, 133, 263, 264, 181, 178, 265, + 412, 134, 375, 134, 134, 377, 376, 453, 707, 378, + 134, 134, 134, 134, 134, 134, 135, 136, 137, 138, + 706, 140, 135, 135, 454, 705, 477, 135, 704, 438, + 135, 135, 135, 478, 135, 135, 136, 137, 138, 439, + 140, 135, 135, 558, 230, 230, 135, 559, 599, 135, + 135, 135, 703, 135, 175, 179, 279, 279, 600, 177, + 175, 175, 702, 701, 700, 175, 699, 175, 175, 181, + + 178, 175, 175, 175, 179, 698, 697, 696, 177, 175, + 175, 695, 694, 693, 175, 692, 175, 175, 181, 178, + 175, 175, 182, 183, 691, 690, 689, 688, 182, 182, + 687, 686, 685, 182, 684, 182, 683, 185, 682, 182, + 182, 182, 183, 681, 680, 222, 223, 182, 182, 224, + 225, 679, 182, 678, 182, 677, 185, 676, 182, 182, + 675, 226, 187, 188, 674, 189, 222, 223, 522, 523, + 224, 225, 673, 672, 671, 670, 524, 669, 190, 668, + 667, 666, 226, 665, 525, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + + 36, 36, 61, 61, 61, 61, 61, 61, 61, 61, + 61, 61, 61, 61, 61, 61, 61, 61, 61, 68, + 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 71, 71, 71, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 85, 85, 85, 85, 85, 85, 85, + 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, + 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, + 86, 86, 86, 86, 86, 86, 86, 91, 91, 91, + 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, + + 91, 91, 91, 91, 107, 107, 107, 107, 107, 107, + 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, + 107, 110, 110, 110, 110, 110, 110, 110, 110, 110, + 110, 110, 110, 110, 110, 110, 110, 110, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 130, 130, 130, 130, 130, 130, 130, 130, + 130, 130, 130, 130, 130, 130, 130, 130, 130, 139, + 139, 139, 139, 139, 139, 139, 139, 139, 139, 139, + + 139, 139, 139, 139, 139, 139, 142, 664, 663, 142, + 142, 662, 661, 660, 659, 658, 657, 656, 142, 142, + 143, 655, 654, 143, 143, 653, 652, 651, 650, 649, + 648, 647, 143, 143, 175, 175, 646, 175, 175, 175, + 175, 175, 175, 175, 175, 645, 175, 175, 175, 175, + 175, 180, 180, 644, 180, 180, 180, 180, 180, 180, + 180, 180, 180, 180, 180, 180, 180, 180, 177, 177, + 643, 177, 177, 177, 177, 177, 177, 177, 177, 642, + 177, 177, 177, 177, 177, 182, 641, 640, 182, 639, + 182, 182, 182, 182, 182, 638, 637, 636, 182, 182, + + 182, 182, 184, 184, 635, 184, 634, 184, 184, 184, + 184, 184, 633, 184, 632, 184, 184, 184, 184, 186, + 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, + 186, 186, 186, 186, 186, 186, 188, 188, 188, 188, + 631, 188, 188, 188, 188, 188, 188, 188, 188, 188, + 630, 188, 188, 194, 629, 628, 627, 626, 625, 624, + 623, 622, 194, 194, 196, 196, 196, 196, 196, 196, + 196, 621, 196, 196, 196, 196, 196, 196, 219, 219, + 620, 219, 219, 219, 219, 219, 219, 219, 219, 219, + 219, 219, 219, 219, 219, 221, 221, 221, 221, 221, + + 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, + 221, 221, 223, 223, 223, 223, 223, 619, 223, 223, + 223, 223, 223, 223, 223, 223, 618, 223, 223, 233, + 617, 616, 233, 233, 615, 614, 613, 612, 611, 193, + 610, 233, 233, 236, 609, 236, 608, 607, 606, 605, + 604, 236, 238, 603, 602, 238, 238, 601, 238, 598, + 597, 596, 238, 595, 238, 238, 278, 594, 278, 593, + 592, 278, 278, 591, 590, 589, 278, 278, 588, 587, + 278, 278, 183, 586, 183, 585, 584, 183, 183, 583, + 582, 193, 183, 580, 579, 578, 183, 183, 280, 577, + + 576, 575, 574, 573, 572, 571, 570, 280, 280, 343, + 569, 568, 567, 566, 565, 564, 563, 562, 343, 343, + 344, 561, 560, 557, 556, 555, 554, 553, 552, 344, + 344, 387, 551, 550, 193, 193, 548, 547, 546, 545, + 387, 387, 388, 544, 543, 542, 541, 540, 539, 538, + 537, 388, 388, 430, 536, 535, 534, 533, 532, 531, + 530, 529, 430, 430, 431, 528, 527, 526, 521, 520, + 519, 518, 517, 431, 431, 475, 516, 515, 193, 193, + 512, 511, 510, 509, 475, 475, 476, 508, 507, 506, + 505, 504, 503, 502, 501, 476, 476, 513, 500, 499, + + 498, 497, 496, 495, 494, 493, 513, 513, 514, 492, + 491, 490, 489, 488, 487, 486, 485, 514, 514, 549, + 484, 483, 482, 481, 480, 479, 193, 193, 549, 549, + 581, 474, 473, 472, 471, 470, 469, 468, 467, 581, + 581, 466, 465, 464, 463, 462, 461, 460, 459, 458, + 455, 452, 451, 450, 449, 448, 447, 446, 445, 444, + 443, 442, 441, 440, 437, 436, 435, 434, 433, 432, + 193, 193, 429, 428, 427, 426, 425, 424, 423, 422, + 421, 420, 419, 418, 417, 416, 415, 414, 413, 410, + 409, 408, 407, 406, 405, 404, 403, 402, 401, 400, + + 399, 398, 397, 396, 395, 394, 393, 392, 391, 390, + 389, 193, 193, 386, 385, 384, 383, 382, 381, 380, + 379, 374, 373, 368, 367, 366, 365, 364, 363, 362, + 361, 360, 359, 358, 357, 356, 355, 354, 351, 348, + 347, 346, 345, 193, 193, 342, 341, 340, 339, 338, + 337, 334, 333, 332, 331, 330, 329, 328, 327, 326, + 325, 324, 321, 320, 319, 318, 317, 316, 315, 314, + 313, 312, 311, 310, 309, 308, 307, 306, 305, 304, + 303, 302, 301, 222, 300, 299, 298, 297, 296, 295, + 294, 293, 292, 287, 286, 285, 284, 283, 282, 281, + + 200, 198, 193, 191, 187, 185, 277, 276, 275, 274, + 273, 272, 269, 268, 267, 266, 260, 255, 252, 251, + 250, 249, 248, 247, 246, 245, 244, 243, 242, 239, + 237, 235, 234, 232, 231, 229, 222, 220, 218, 212, + 211, 206, 203, 200, 199, 198, 197, 195, 193, 192, + 191, 187, 172, 158, 155, 154, 150, 141, 713, 129, + 129, 31, 713, 713, 713, 713, 713, 713, 713, 713, + 713, 713, 713, 713, 713, 713, 713, 713, 713, 713, + 713, 713, 713, 713, 713, 713, 713, 713, 713, 713, + 713, 713, 713, 713, 713, 713, 713, 713, 713, 713, + + 713, 713, 713, 713, 713, 713, 713 + } ; + +static yyconst flex_int16_t yy_chk[1508] = + { 0, + 1, 1, 1, 1, 0, 1, 1, 1, 17, 17, + 0, 1, 0, 1, 1, 1, 1, 1, 1, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 3, 3, 3, 3, 48, 3, + 3, 3, 3, 18, 18, 3, 48, 3, 3, 3, + 3, 3, 3, 4, 4, 4, 4, 50, 4, 4, + 4, 4, 115, 115, 4, 709, 4, 4, 4, 4, + + 4, 4, 5, 5, 5, 5, 53, 5, 5, 5, + 5, 704, 53, 5, 50, 5, 5, 5, 5, 5, + 5, 6, 6, 6, 6, 100, 6, 6, 6, 6, + 7, 7, 6, 7, 6, 6, 6, 6, 6, 6, + 8, 8, 54, 8, 43, 100, 7, 54, 43, 96, + 43, 157, 157, 208, 96, 208, 8, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + + 9, 9, 11, 11, 11, 11, 12, 12, 12, 12, + 13, 13, 13, 14, 14, 14, 11, 41, 19, 19, + 12, 13, 19, 19, 14, 15, 15, 15, 20, 20, + 62, 41, 20, 20, 19, 41, 15, 101, 52, 328, + 98, 39, 52, 15, 20, 62, 15, 56, 15, 15, + 39, 101, 15, 98, 52, 15, 15, 15, 159, 39, + 51, 15, 15, 328, 51, 15, 16, 16, 16, 56, + 105, 57, 104, 159, 51, 57, 104, 16, 51, 71, + 71, 145, 71, 105, 16, 160, 57, 16, 145, 16, + 16, 160, 104, 16, 209, 71, 16, 16, 16, 226, + + 226, 209, 16, 16, 278, 278, 16, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 23, 23, 23, 23, 308, 23, 23, 23, + 308, 411, 411, 23, 270, 23, 23, 23, 23, 23, + 23, 167, 176, 270, 23, 24, 24, 24, 24, 175, + 24, 24, 24, 175, 703, 167, 24, 176, 24, 24, + 24, 24, 24, 24, 175, 258, 177, 24, 27, 27, + + 27, 329, 258, 368, 306, 329, 701, 27, 700, 27, + 27, 177, 162, 179, 306, 162, 27, 27, 27, 27, + 27, 27, 28, 28, 28, 162, 162, 179, 179, 162, + 368, 28, 333, 28, 28, 334, 333, 409, 699, 334, + 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, + 698, 29, 29, 29, 409, 697, 433, 29, 696, 395, + 29, 29, 29, 433, 29, 30, 30, 30, 30, 395, + 30, 30, 30, 524, 741, 741, 30, 524, 569, 30, + 30, 30, 695, 30, 61, 61, 747, 747, 569, 61, + 61, 61, 694, 693, 692, 61, 691, 61, 61, 61, + + 61, 61, 61, 63, 63, 690, 688, 687, 63, 63, + 63, 686, 685, 684, 63, 683, 63, 63, 63, 63, + 63, 63, 68, 68, 680, 679, 678, 677, 68, 68, + 676, 675, 674, 68, 673, 68, 672, 68, 671, 68, + 68, 70, 70, 670, 669, 110, 110, 70, 70, 110, + 110, 668, 70, 665, 70, 664, 70, 663, 70, 70, + 662, 110, 186, 186, 661, 186, 221, 221, 484, 484, + 221, 221, 660, 659, 657, 656, 484, 655, 186, 654, + 653, 652, 221, 651, 484, 714, 714, 714, 714, 714, + 714, 714, 714, 714, 714, 714, 714, 714, 714, 714, + + 714, 714, 715, 715, 715, 715, 715, 715, 715, 715, + 715, 715, 715, 715, 715, 715, 715, 715, 715, 716, + 716, 716, 716, 716, 716, 716, 716, 716, 716, 716, + 716, 716, 716, 716, 716, 716, 717, 717, 717, 717, + 717, 717, 717, 717, 717, 717, 717, 717, 717, 717, + 717, 717, 717, 718, 718, 718, 718, 718, 718, 718, + 718, 718, 718, 718, 718, 718, 718, 718, 718, 718, + 719, 719, 719, 719, 719, 719, 719, 719, 719, 719, + 719, 719, 719, 719, 719, 719, 719, 720, 720, 720, + 720, 720, 720, 720, 720, 720, 720, 720, 720, 720, + + 720, 720, 720, 720, 721, 721, 721, 721, 721, 721, + 721, 721, 721, 721, 721, 721, 721, 721, 721, 721, + 721, 722, 722, 722, 722, 722, 722, 722, 722, 722, + 722, 722, 722, 722, 722, 722, 722, 722, 723, 723, + 723, 723, 723, 723, 723, 723, 723, 723, 723, 723, + 723, 723, 723, 723, 723, 724, 724, 724, 724, 724, + 724, 724, 724, 724, 724, 724, 724, 724, 724, 724, + 724, 724, 725, 725, 725, 725, 725, 725, 725, 725, + 725, 725, 725, 725, 725, 725, 725, 725, 725, 726, + 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, + + 726, 726, 726, 726, 726, 726, 727, 650, 648, 727, + 727, 646, 645, 644, 643, 642, 641, 640, 727, 727, + 728, 639, 637, 728, 728, 636, 635, 634, 633, 631, + 630, 628, 728, 728, 729, 729, 627, 729, 729, 729, + 729, 729, 729, 729, 729, 626, 729, 729, 729, 729, + 729, 730, 730, 625, 730, 730, 730, 730, 730, 730, + 730, 730, 730, 730, 730, 730, 730, 730, 731, 731, + 624, 731, 731, 731, 731, 731, 731, 731, 731, 619, + 731, 731, 731, 731, 731, 732, 618, 617, 732, 616, + 732, 732, 732, 732, 732, 615, 614, 612, 732, 732, + + 732, 732, 733, 733, 611, 733, 610, 733, 733, 733, + 733, 733, 608, 733, 607, 733, 733, 733, 733, 734, + 734, 734, 734, 734, 734, 734, 734, 734, 734, 734, + 734, 734, 734, 734, 734, 734, 735, 735, 735, 735, + 606, 735, 735, 735, 735, 735, 735, 735, 735, 735, + 604, 735, 735, 736, 602, 601, 600, 599, 598, 597, + 596, 594, 736, 736, 737, 737, 737, 737, 737, 737, + 737, 593, 737, 737, 737, 737, 737, 737, 738, 738, + 591, 738, 738, 738, 738, 738, 738, 738, 738, 738, + 738, 738, 738, 738, 738, 739, 739, 739, 739, 739, + + 739, 739, 739, 739, 739, 739, 739, 739, 739, 739, + 739, 739, 740, 740, 740, 740, 740, 590, 740, 740, + 740, 740, 740, 740, 740, 740, 589, 740, 740, 742, + 588, 587, 742, 742, 586, 585, 584, 583, 582, 581, + 580, 742, 742, 743, 578, 743, 577, 576, 575, 574, + 573, 743, 744, 572, 571, 744, 744, 570, 744, 568, + 567, 566, 744, 565, 744, 744, 745, 564, 745, 563, + 562, 745, 745, 560, 559, 558, 745, 745, 557, 556, + 745, 745, 746, 555, 746, 554, 553, 746, 746, 551, + 550, 549, 746, 548, 547, 546, 746, 746, 748, 543, + + 542, 541, 540, 539, 538, 536, 535, 748, 748, 749, + 534, 533, 532, 531, 530, 529, 528, 527, 749, 749, + 750, 526, 525, 523, 522, 520, 519, 518, 517, 750, + 750, 751, 516, 515, 514, 513, 512, 511, 510, 509, + 751, 751, 752, 508, 507, 506, 504, 503, 502, 499, + 497, 752, 752, 753, 496, 495, 494, 492, 491, 490, + 489, 488, 753, 753, 754, 487, 486, 485, 483, 482, + 481, 480, 479, 754, 754, 755, 478, 477, 476, 475, + 474, 472, 471, 470, 755, 755, 756, 469, 468, 466, + 465, 464, 463, 461, 460, 756, 756, 757, 459, 458, + + 457, 456, 455, 454, 453, 451, 757, 757, 758, 450, + 449, 448, 447, 446, 445, 444, 442, 758, 758, 759, + 441, 439, 437, 436, 435, 434, 431, 430, 759, 759, + 760, 429, 428, 427, 426, 425, 424, 423, 422, 760, + 760, 421, 420, 419, 417, 416, 415, 414, 413, 412, + 410, 408, 407, 406, 405, 404, 403, 402, 401, 400, + 399, 398, 397, 396, 394, 393, 392, 391, 390, 389, + 388, 387, 386, 385, 384, 383, 382, 381, 380, 379, + 378, 377, 376, 375, 373, 372, 371, 370, 369, 367, + 366, 365, 364, 363, 362, 361, 360, 359, 358, 357, + + 356, 354, 353, 352, 351, 350, 349, 348, 347, 346, + 345, 344, 343, 342, 341, 340, 339, 338, 337, 336, + 335, 331, 330, 327, 326, 324, 323, 322, 321, 320, + 319, 318, 317, 316, 315, 313, 312, 311, 307, 305, + 304, 303, 302, 280, 279, 277, 276, 274, 273, 272, + 271, 269, 268, 267, 266, 265, 264, 263, 262, 261, + 260, 259, 257, 256, 255, 254, 253, 252, 251, 250, + 249, 248, 247, 246, 245, 244, 243, 242, 241, 240, + 239, 234, 231, 223, 218, 217, 216, 215, 214, 213, + 212, 211, 210, 207, 206, 205, 204, 203, 202, 201, + + 200, 198, 194, 190, 188, 183, 174, 173, 172, 171, + 170, 168, 166, 165, 164, 163, 161, 158, 156, 155, + 154, 153, 152, 151, 150, 149, 148, 147, 146, 144, + 138, 133, 127, 125, 121, 119, 112, 109, 106, 103, + 102, 99, 97, 95, 94, 90, 89, 84, 80, 79, + 75, 73, 55, 49, 45, 44, 42, 35, 31, 26, + 25, 713, 713, 713, 713, 713, 713, 713, 713, 713, + 713, 713, 713, 713, 713, 713, 713, 713, 713, 713, + 713, 713, 713, 713, 713, 713, 713, 713, 713, 713, + 713, 713, 713, 713, 713, 713, 713, 713, 713, 713, + + 713, 713, 713, 713, 713, 713, 713 + } ; + +extern int yy_flex_debug; +int yy_flex_debug = 0; + +static yy_state_type *yy_state_buf=0, *yy_state_ptr=0; +static char *yy_full_match; +static int yy_lp; +static int yy_looking_for_trail_begin = 0; +static int yy_full_lp; +static int *yy_full_state; +#define YY_TRAILING_MASK 0x2000 +#define YY_TRAILING_HEAD_MASK 0x4000 +#define REJECT \ +{ \ +*yy_cp = (yy_hold_char); /* undo effects of setting up yytext */ \ +yy_cp = (yy_full_match); /* restore poss. backed-over text */ \ +(yy_lp) = yy_full_lp; /* restore orig. accepting pos. */ \ +(yy_state_ptr) = yy_full_state; /* restore orig. state */ \ +yy_current_state = *(yy_state_ptr); /* restore curr. state */ \ +++(yy_lp); \ +goto find_rule; \ +} + +static int yy_more_flag = 0; +static int yy_more_len = 0; +#define yymore() ((yy_more_flag) = 1) +#define YY_MORE_ADJ (yy_more_len) +#define YY_RESTORE_YY_MORE_OFFSET +char *yytext; +#line 1 "scanner.lxx" +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#line 20 "scanner.lxx" +#include +#include +#include +#include +#include "parse_ctrl.h" +#include "parser.h" +#include "util.h" +#include "audits/memman.h" + +using namespace std; + + + + + + + + + + + + + + +#line 1240 "scanner.cxx" + +#define INITIAL 0 +#define C_URI 1 +#define C_URI_SPECIAL 2 +#define C_QSTRING 3 +#define C_LANG 4 +#define C_WORD 5 +#define C_NUM 6 +#define C_DATE 7 +#define C_LINE 8 +#define C_COMMENT 9 +#define C_NEW 10 +#define C_AUTH_SCHEME 11 +#define C_RPAREN 12 +#define C_IPV6ADDR 13 +#define C_PARAMVAL 14 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +static int yy_init_globals (void ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap (void ); +#else +extern int yywrap (void ); +#endif +#endif + + static void yyunput (int c,char *buf_ptr ); + +#ifndef yytext_ptr +static void yy_flex_strncpy (char *,yyconst char *,int ); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * ); +#endif + +#ifndef YY_NO_INPUT + +#ifdef __cplusplus +static int yyinput (void ); +#else +static int input (void ); +#endif + +#endif + + static int yy_start_stack_ptr = 0; + static int yy_start_stack_depth = 0; + static int *yy_start_stack = NULL; + + static void yy_push_state (int new_state ); + + static void yy_pop_state (void ); + + static int yy_top_state (void ); + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#define YY_READ_BUF_SIZE 8192 +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO (void) fwrite( yytext, yyleng, 1, yyout ) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + size_t n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg ) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex (void); + +#define YY_DECL int yylex (void) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK break; +#endif + +#define YY_RULE_SETUP \ + if ( yyleng > 0 ) \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = \ + (yytext[yyleng - 1] == '\n'); \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + register yy_state_type yy_current_state; + register char *yy_cp, *yy_bp; + register int yy_act; + +#line 58 "scanner.lxx" + + 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 */ +#line 1439 "scanner.cxx" + + if ( !(yy_init) ) + { + (yy_init) = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + /* Create the reject buffer large enough to save one state per allowed character. */ + if ( ! (yy_state_buf) ) + (yy_state_buf) = (yy_state_type *)yyalloc(YY_STATE_BUF_SIZE ); + + if ( ! (yy_start) ) + (yy_start) = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer(yyin,YY_BUF_SIZE ); + } + + yy_load_buffer_state( ); + } + + while ( 1 ) /* loops until end-of-file is reached */ + { + (yy_more_len) = 0; + if ( (yy_more_flag) ) + { + (yy_more_len) = (yy_c_buf_p) - (yytext_ptr); + (yy_more_flag) = 0; + } + yy_cp = (yy_c_buf_p); + + /* Support of yytext. */ + *yy_cp = (yy_hold_char); + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = (yy_start); + yy_current_state += YY_AT_BOL(); + + (yy_state_ptr) = (yy_state_buf); + *(yy_state_ptr)++ = yy_current_state; + +yy_match: + do + { + register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)]; + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 714 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + *(yy_state_ptr)++ = yy_current_state; + ++yy_cp; + } + while ( yy_base[yy_current_state] != 1462 ); + +yy_find_action: + yy_current_state = *--(yy_state_ptr); + (yy_lp) = yy_accept[yy_current_state]; +find_rule: /* we branch to this label when backing up */ + for ( ; ; ) /* until we find what rule we matched */ + { + if ( (yy_lp) && (yy_lp) < yy_accept[yy_current_state + 1] ) + { + yy_act = yy_acclist[(yy_lp)]; + if ( yy_act & YY_TRAILING_HEAD_MASK || + yy_looking_for_trail_begin ) + { + if ( yy_act == yy_looking_for_trail_begin ) + { + yy_looking_for_trail_begin = 0; + yy_act &= ~YY_TRAILING_HEAD_MASK; + break; + } + } + else if ( yy_act & YY_TRAILING_MASK ) + { + yy_looking_for_trail_begin = yy_act & ~YY_TRAILING_MASK; + yy_looking_for_trail_begin |= YY_TRAILING_HEAD_MASK; + } + else + { + (yy_full_match) = yy_cp; + yy_full_state = (yy_state_ptr); + yy_full_lp = (yy_lp); + break; + } + ++(yy_lp); + goto find_rule; + } + --yy_cp; + yy_current_state = *--(yy_state_ptr); + (yy_lp) = yy_accept[yy_current_state]; + } + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ +case 1: +YY_RULE_SETUP +#line 76 "scanner.lxx" +{ return T_HDR_ACCEPT; } + YY_BREAK +case 2: +YY_RULE_SETUP +#line 77 "scanner.lxx" +{ return T_HDR_ACCEPT_ENCODING; } + YY_BREAK +case 3: +YY_RULE_SETUP +#line 78 "scanner.lxx" +{ return T_HDR_ACCEPT_LANGUAGE; } + YY_BREAK +case 4: +YY_RULE_SETUP +#line 79 "scanner.lxx" +{ return T_HDR_ALERT_INFO; } + YY_BREAK +case 5: +YY_RULE_SETUP +#line 80 "scanner.lxx" +{ return T_HDR_ALLOW; } + YY_BREAK +case 6: +YY_RULE_SETUP +#line 81 "scanner.lxx" +{ return T_HDR_ALLOW_EVENTS; } + YY_BREAK +case 7: +YY_RULE_SETUP +#line 82 "scanner.lxx" +{ return T_HDR_AUTHENTICATION_INFO; } + YY_BREAK +case 8: +YY_RULE_SETUP +#line 83 "scanner.lxx" +{ return T_HDR_AUTHORIZATION; } + YY_BREAK +case 9: +YY_RULE_SETUP +#line 84 "scanner.lxx" +{ return T_HDR_CALL_ID; } + YY_BREAK +case 10: +YY_RULE_SETUP +#line 85 "scanner.lxx" +{ return T_HDR_CALL_INFO; } + YY_BREAK +case 11: +YY_RULE_SETUP +#line 86 "scanner.lxx" +{ return T_HDR_CONTACT; } + YY_BREAK +case 12: +YY_RULE_SETUP +#line 87 "scanner.lxx" +{ return T_HDR_CONTENT_DISP; } + YY_BREAK +case 13: +YY_RULE_SETUP +#line 88 "scanner.lxx" +{ return T_HDR_CONTENT_ENCODING; } + YY_BREAK +case 14: +YY_RULE_SETUP +#line 89 "scanner.lxx" +{ return T_HDR_CONTENT_LANGUAGE; } + YY_BREAK +case 15: +YY_RULE_SETUP +#line 90 "scanner.lxx" +{ return T_HDR_CONTENT_LENGTH; } + YY_BREAK +case 16: +YY_RULE_SETUP +#line 91 "scanner.lxx" +{ return T_HDR_CONTENT_TYPE; } + YY_BREAK +case 17: +YY_RULE_SETUP +#line 92 "scanner.lxx" +{ return T_HDR_CSEQ; } + YY_BREAK +case 18: +YY_RULE_SETUP +#line 93 "scanner.lxx" +{ return T_HDR_DATE; } + YY_BREAK +case 19: +YY_RULE_SETUP +#line 94 "scanner.lxx" +{ return T_HDR_ERROR_INFO; } + YY_BREAK +case 20: +YY_RULE_SETUP +#line 95 "scanner.lxx" +{ return T_HDR_EVENT; } + YY_BREAK +case 21: +YY_RULE_SETUP +#line 96 "scanner.lxx" +{ return T_HDR_EXPIRES; } + YY_BREAK +case 22: +YY_RULE_SETUP +#line 97 "scanner.lxx" +{ return T_HDR_FROM; } + YY_BREAK +case 23: +YY_RULE_SETUP +#line 98 "scanner.lxx" +{ return T_HDR_IN_REPLY_TO; } + YY_BREAK +case 24: +YY_RULE_SETUP +#line 99 "scanner.lxx" +{ return T_HDR_MAX_FORWARDS; } + YY_BREAK +case 25: +YY_RULE_SETUP +#line 100 "scanner.lxx" +{ return T_HDR_MIN_EXPIRES; } + YY_BREAK +case 26: +YY_RULE_SETUP +#line 101 "scanner.lxx" +{ return T_HDR_MIME_VERSION; } + YY_BREAK +case 27: +YY_RULE_SETUP +#line 102 "scanner.lxx" +{ return T_HDR_ORGANIZATION; } + YY_BREAK +case 28: +YY_RULE_SETUP +#line 103 "scanner.lxx" +{ return T_HDR_P_ASSERTED_IDENTITY; } + YY_BREAK +case 29: +YY_RULE_SETUP +#line 104 "scanner.lxx" +{ return T_HDR_P_PREFERRED_IDENTITY; } + YY_BREAK +case 30: +YY_RULE_SETUP +#line 105 "scanner.lxx" +{ return T_HDR_PRIORITY; } + YY_BREAK +case 31: +YY_RULE_SETUP +#line 106 "scanner.lxx" +{ return T_HDR_PRIVACY; } + YY_BREAK +case 32: +YY_RULE_SETUP +#line 107 "scanner.lxx" +{ return T_HDR_PROXY_AUTHENTICATE; } + YY_BREAK +case 33: +YY_RULE_SETUP +#line 108 "scanner.lxx" +{ return T_HDR_PROXY_AUTHORIZATION; } + YY_BREAK +case 34: +YY_RULE_SETUP +#line 109 "scanner.lxx" +{ return T_HDR_PROXY_REQUIRE; } + YY_BREAK +case 35: +YY_RULE_SETUP +#line 110 "scanner.lxx" +{ return T_HDR_RACK; } + YY_BREAK +case 36: +YY_RULE_SETUP +#line 111 "scanner.lxx" +{ return T_HDR_RECORD_ROUTE; } + YY_BREAK +case 37: +YY_RULE_SETUP +#line 112 "scanner.lxx" +{ return T_HDR_SERVICE_ROUTE; } + YY_BREAK +case 38: +YY_RULE_SETUP +#line 113 "scanner.lxx" +{ return T_HDR_REFER_SUB; } + YY_BREAK +case 39: +YY_RULE_SETUP +#line 114 "scanner.lxx" +{ return T_HDR_REFER_TO; } + YY_BREAK +case 40: +YY_RULE_SETUP +#line 115 "scanner.lxx" +{ return T_HDR_REFERRED_BY; } + YY_BREAK +case 41: +YY_RULE_SETUP +#line 116 "scanner.lxx" +{ return T_HDR_REPLACES; } + YY_BREAK +case 42: +YY_RULE_SETUP +#line 117 "scanner.lxx" +{ return T_HDR_REPLY_TO; } + YY_BREAK +case 43: +YY_RULE_SETUP +#line 118 "scanner.lxx" +{ return T_HDR_REQUIRE; } + YY_BREAK +case 44: +YY_RULE_SETUP +#line 119 "scanner.lxx" +{return T_HDR_REQUEST_DISPOSITION; } + YY_BREAK +case 45: +YY_RULE_SETUP +#line 120 "scanner.lxx" +{ return T_HDR_RETRY_AFTER; } + YY_BREAK +case 46: +YY_RULE_SETUP +#line 121 "scanner.lxx" +{ return T_HDR_ROUTE; } + YY_BREAK +case 47: +YY_RULE_SETUP +#line 122 "scanner.lxx" +{ return T_HDR_RSEQ; } + YY_BREAK +case 48: +YY_RULE_SETUP +#line 123 "scanner.lxx" +{ return T_HDR_SERVER; } + YY_BREAK +case 49: +YY_RULE_SETUP +#line 124 "scanner.lxx" +{ return T_HDR_SIP_ETAG; } + YY_BREAK +case 50: +YY_RULE_SETUP +#line 125 "scanner.lxx" +{ return T_HDR_SIP_IF_MATCH; } + YY_BREAK +case 51: +YY_RULE_SETUP +#line 126 "scanner.lxx" +{ return T_HDR_SUBJECT; } + YY_BREAK +case 52: +YY_RULE_SETUP +#line 127 "scanner.lxx" +{ return T_HDR_SUBSCRIPTION_STATE; } + YY_BREAK +case 53: +YY_RULE_SETUP +#line 128 "scanner.lxx" +{ return T_HDR_SUPPORTED; } + YY_BREAK +case 54: +YY_RULE_SETUP +#line 129 "scanner.lxx" +{ return T_HDR_TIMESTAMP; } + YY_BREAK +case 55: +YY_RULE_SETUP +#line 130 "scanner.lxx" +{ return T_HDR_TO; } + YY_BREAK +case 56: +YY_RULE_SETUP +#line 131 "scanner.lxx" +{ return T_HDR_UNSUPPORTED; } + YY_BREAK +case 57: +YY_RULE_SETUP +#line 132 "scanner.lxx" +{ return T_HDR_USER_AGENT; } + YY_BREAK +case 58: +YY_RULE_SETUP +#line 133 "scanner.lxx" +{ return T_HDR_VIA; } + YY_BREAK +case 59: +YY_RULE_SETUP +#line 134 "scanner.lxx" +{ return T_HDR_WARNING; } + YY_BREAK +case 60: +YY_RULE_SETUP +#line 135 "scanner.lxx" +{ return T_HDR_WWW_AUTHENTICATE; } + YY_BREAK +case 61: +YY_RULE_SETUP +#line 136 "scanner.lxx" +{ yylval.yyt_str = new string(yytext); + MEMMAN_NEW(yylval.yyt_str); + return T_HDR_UNKNOWN; } + YY_BREAK +/* Token as define in RFC 3261 */ +case 62: +YY_RULE_SETUP +#line 141 "scanner.lxx" +{ yylval.yyt_str = new string(yytext); + MEMMAN_NEW(yylval.yyt_str); + return T_TOKEN; } + YY_BREAK +/* Switch to quoted string context */ +case 63: +YY_RULE_SETUP +#line 146 "scanner.lxx" +{ yy_push_state(C_QSTRING); } + YY_BREAK +/* End of line */ +case 64: +/* rule 64 can match eol */ +YY_RULE_SETUP +#line 149 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 65: +/* rule 65 can match eol */ +YY_RULE_SETUP +#line 150 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 66: +YY_RULE_SETUP +#line 152 "scanner.lxx" +/* Skip white space */ + YY_BREAK +/* Single character token */ +case 67: +YY_RULE_SETUP +#line 155 "scanner.lxx" +{ return yytext[0]; } + YY_BREAK +/* URI. + This context scans a URI including parameters. + The syntax of a URI will be checked outside the scanner + */ +case 68: +YY_RULE_SETUP +#line 161 "scanner.lxx" +{ yy_push_state(C_QSTRING); } + YY_BREAK +case 69: +*yy_cp = (yy_hold_char); /* undo effects of setting up yytext */ +(yy_c_buf_p) = yy_cp -= 1; +YY_DO_BEFORE_ACTION; /* set up yytext again */ +YY_RULE_SETUP +#line 162 "scanner.lxx" +{ + yylval.yyt_str = new string(yytext); + MEMMAN_NEW(yylval.yyt_str); + return T_DISPLAY; } + YY_BREAK +case 70: +YY_RULE_SETUP +#line 166 "scanner.lxx" +{ + yylval.yyt_str = new string(yytext); + MEMMAN_NEW(yylval.yyt_str); + return T_URI; } + YY_BREAK +case 71: +YY_RULE_SETUP +#line 170 "scanner.lxx" +{ return T_URI_WILDCARD; } + YY_BREAK +case 72: +YY_RULE_SETUP +#line 171 "scanner.lxx" +{ + yylval.yyt_str = new string(yytext); + MEMMAN_NEW(yylval.yyt_str); + return T_URI; } + YY_BREAK +case 73: +YY_RULE_SETUP +#line 175 "scanner.lxx" +/* Skip white space */ + YY_BREAK +case 74: +YY_RULE_SETUP +#line 176 "scanner.lxx" +{ return yytext[0]; } + YY_BREAK +case 75: +/* rule 75 can match eol */ +YY_RULE_SETUP +#line 177 "scanner.lxx" +{ return T_ERROR; } + YY_BREAK +/* 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 + */ +case 76: +YY_RULE_SETUP +#line 190 "scanner.lxx" +{ yy_push_state(C_QSTRING); } + YY_BREAK +case 77: +*yy_cp = (yy_hold_char); /* undo effects of setting up yytext */ +(yy_c_buf_p) = yy_cp -= 1; +YY_DO_BEFORE_ACTION; /* set up yytext again */ +YY_RULE_SETUP +#line 191 "scanner.lxx" +{ + yylval.yyt_str = new string(yytext); + MEMMAN_NEW(yylval.yyt_str); + return T_DISPLAY; } + YY_BREAK +case 78: +YY_RULE_SETUP +#line 195 "scanner.lxx" +{ return T_URI_WILDCARD; } + YY_BREAK +case 79: +YY_RULE_SETUP +#line 196 "scanner.lxx" +{ + yylval.yyt_str = new string(yytext); + MEMMAN_NEW(yylval.yyt_str); + return T_URI; } + YY_BREAK +case 80: +YY_RULE_SETUP +#line 200 "scanner.lxx" +/* Skip white space */ + YY_BREAK +case 81: +YY_RULE_SETUP +#line 201 "scanner.lxx" +{ return yytext[0]; } + YY_BREAK +case 82: +/* rule 82 can match eol */ +YY_RULE_SETUP +#line 202 "scanner.lxx" +{ return T_ERROR; } + YY_BREAK +/* Quoted string (starting after open quote, closing quote + will be consumed but not returned. */ +case 83: +YY_RULE_SETUP +#line 206 "scanner.lxx" +{ yymore(); } + YY_BREAK +case 84: +YY_RULE_SETUP +#line 207 "scanner.lxx" +{ yymore(); } + YY_BREAK +case 85: +YY_RULE_SETUP +#line 208 "scanner.lxx" +{ yy_pop_state(); + yytext[strlen(yytext)-1] = '\0'; + yylval.yyt_str = new string(unescape(string(yytext))); + MEMMAN_NEW(yylval.yyt_str); + return T_QSTRING; } + YY_BREAK +case 86: +/* rule 86 can match eol */ +YY_RULE_SETUP +#line 213 "scanner.lxx" +{ yy_pop_state(); return T_ERROR; } + YY_BREAK +case 87: +YY_RULE_SETUP +#line 214 "scanner.lxx" +{ yy_pop_state(); return T_ERROR; } + YY_BREAK +/* Comment (starting after LPAREN till RPAREN) */ +case 88: +YY_RULE_SETUP +#line 217 "scanner.lxx" +{ yymore(); } + YY_BREAK +case 89: +YY_RULE_SETUP +#line 218 "scanner.lxx" +{ yymore(); } + YY_BREAK +case 90: +YY_RULE_SETUP +#line 219 "scanner.lxx" +{ yymore(); } + YY_BREAK +case 91: +YY_RULE_SETUP +#line 220 "scanner.lxx" +{ t_parser::inc_comment_level(); yymore(); } + YY_BREAK +case 92: +*yy_cp = (yy_hold_char); /* undo effects of setting up yytext */ +(yy_c_buf_p) = yy_cp -= 1; +YY_DO_BEFORE_ACTION; /* set up yytext again */ +YY_RULE_SETUP +#line 221 "scanner.lxx" +{ 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; + } + } + YY_BREAK +case 93: +/* rule 93 can match eol */ +YY_RULE_SETUP +#line 230 "scanner.lxx" +{ return T_ERROR; } + YY_BREAK +case 94: +YY_RULE_SETUP +#line 231 "scanner.lxx" +{ return T_ERROR; } + YY_BREAK +case 95: +YY_RULE_SETUP +#line 232 "scanner.lxx" +{ BEGIN(C_COMMENT); yymore(); } + YY_BREAK +/* Language tag */ +case 96: +YY_RULE_SETUP +#line 235 "scanner.lxx" +{ yylval.yyt_str = new string(yytext); + MEMMAN_NEW(yylval.yyt_str); + return T_LANG; } + YY_BREAK +case 97: +YY_RULE_SETUP +#line 238 "scanner.lxx" +/* Skip white space */ + YY_BREAK +case 98: +YY_RULE_SETUP +#line 239 "scanner.lxx" +{ return yytext[0]; } + YY_BREAK +case 99: +/* rule 99 can match eol */ +YY_RULE_SETUP +#line 240 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 100: +/* rule 100 can match eol */ +YY_RULE_SETUP +#line 241 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +/* Word */ +case 101: +YY_RULE_SETUP +#line 244 "scanner.lxx" +{ yylval.yyt_str = new string(yytext); + MEMMAN_NEW(yylval.yyt_str); + return T_WORD; } + YY_BREAK +case 102: +YY_RULE_SETUP +#line 247 "scanner.lxx" +/* Skip white space */ + YY_BREAK +case 103: +YY_RULE_SETUP +#line 248 "scanner.lxx" +{ return yytext[0]; } + YY_BREAK +case 104: +/* rule 104 can match eol */ +YY_RULE_SETUP +#line 249 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 105: +/* rule 105 can match eol */ +YY_RULE_SETUP +#line 250 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +/* Number */ +case 106: +YY_RULE_SETUP +#line 253 "scanner.lxx" +{ yylval.yyt_ulong = strtoul(yytext, NULL, 10); return T_NUM; } + YY_BREAK +case 107: +YY_RULE_SETUP +#line 254 "scanner.lxx" +/* Skip white space */ + YY_BREAK +case 108: +YY_RULE_SETUP +#line 255 "scanner.lxx" +{ return yytext[0]; } + YY_BREAK +case 109: +/* rule 109 can match eol */ +YY_RULE_SETUP +#line 256 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 110: +/* rule 110 can match eol */ +YY_RULE_SETUP +#line 257 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +/* Date */ +case 111: +YY_RULE_SETUP +#line 260 "scanner.lxx" +{ yylval.yyt_int = 1; return T_WKDAY; } + YY_BREAK +case 112: +YY_RULE_SETUP +#line 261 "scanner.lxx" +{ yylval.yyt_int = 2; return T_WKDAY; } + YY_BREAK +case 113: +YY_RULE_SETUP +#line 262 "scanner.lxx" +{ yylval.yyt_int = 3; return T_WKDAY; } + YY_BREAK +case 114: +YY_RULE_SETUP +#line 263 "scanner.lxx" +{ yylval.yyt_int = 4; return T_WKDAY; } + YY_BREAK +case 115: +YY_RULE_SETUP +#line 264 "scanner.lxx" +{ yylval.yyt_int = 5; return T_WKDAY; } + YY_BREAK +case 116: +YY_RULE_SETUP +#line 265 "scanner.lxx" +{ yylval.yyt_int = 6; return T_WKDAY; } + YY_BREAK +case 117: +YY_RULE_SETUP +#line 266 "scanner.lxx" +{ yylval.yyt_int = 0; return T_WKDAY; } + YY_BREAK +case 118: +YY_RULE_SETUP +#line 267 "scanner.lxx" +{ yylval.yyt_int = 0; return T_MONTH; } + YY_BREAK +case 119: +YY_RULE_SETUP +#line 268 "scanner.lxx" +{ yylval.yyt_int = 1; return T_MONTH; } + YY_BREAK +case 120: +YY_RULE_SETUP +#line 269 "scanner.lxx" +{ yylval.yyt_int = 2; return T_MONTH; } + YY_BREAK +case 121: +YY_RULE_SETUP +#line 270 "scanner.lxx" +{ yylval.yyt_int = 3; return T_MONTH; } + YY_BREAK +case 122: +YY_RULE_SETUP +#line 271 "scanner.lxx" +{ yylval.yyt_int = 4; return T_MONTH; } + YY_BREAK +case 123: +YY_RULE_SETUP +#line 272 "scanner.lxx" +{ yylval.yyt_int = 5; return T_MONTH; } + YY_BREAK +case 124: +YY_RULE_SETUP +#line 273 "scanner.lxx" +{ yylval.yyt_int = 6; return T_MONTH; } + YY_BREAK +case 125: +YY_RULE_SETUP +#line 274 "scanner.lxx" +{ yylval.yyt_int = 7; return T_MONTH; } + YY_BREAK +case 126: +YY_RULE_SETUP +#line 275 "scanner.lxx" +{ yylval.yyt_int = 8; return T_MONTH; } + YY_BREAK +case 127: +YY_RULE_SETUP +#line 276 "scanner.lxx" +{ yylval.yyt_int = 9; return T_MONTH; } + YY_BREAK +case 128: +YY_RULE_SETUP +#line 277 "scanner.lxx" +{ yylval.yyt_int = 10; return T_MONTH; } + YY_BREAK +case 129: +YY_RULE_SETUP +#line 278 "scanner.lxx" +{ yylval.yyt_int = 11; return T_MONTH; } + YY_BREAK +case 130: +YY_RULE_SETUP +#line 279 "scanner.lxx" +{ return T_GMT; } + YY_BREAK +case 131: +YY_RULE_SETUP +#line 280 "scanner.lxx" +{ yylval.yyt_ulong = strtoul(yytext, NULL, 10); return T_NUM; } + YY_BREAK +case 132: +YY_RULE_SETUP +#line 281 "scanner.lxx" +/* Skip white space */ + YY_BREAK +case 133: +YY_RULE_SETUP +#line 282 "scanner.lxx" +{ return yytext[0]; } + YY_BREAK +case 134: +/* rule 134 can match eol */ +YY_RULE_SETUP +#line 283 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 135: +/* rule 135 can match eol */ +YY_RULE_SETUP +#line 284 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +/* Get all text till end of line */ +case 136: +YY_RULE_SETUP +#line 287 "scanner.lxx" +{ yylval.yyt_str = new string(yytext); + MEMMAN_NEW(yylval.yyt_str); + return T_LINE; } + YY_BREAK +case 137: +/* rule 137 can match eol */ +YY_RULE_SETUP +#line 290 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 138: +/* rule 138 can match eol */ +YY_RULE_SETUP +#line 291 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 139: +YY_RULE_SETUP +#line 292 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +/* Start of a new message */ +case 140: +YY_RULE_SETUP +#line 295 "scanner.lxx" +{ return T_SIP; } + YY_BREAK +case 141: +YY_RULE_SETUP +#line 296 "scanner.lxx" +{ yylval.yyt_str = new string(yytext); + MEMMAN_NEW(yylval.yyt_str); + return T_METHOD; } + YY_BREAK +case 142: +YY_RULE_SETUP +#line 299 "scanner.lxx" +/* Skip white space */ + YY_BREAK +case 143: +YY_RULE_SETUP +#line 300 "scanner.lxx" +{ return T_ERROR; } + YY_BREAK +case 144: +/* rule 144 can match eol */ +YY_RULE_SETUP +#line 301 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 145: +/* rule 145 can match eol */ +YY_RULE_SETUP +#line 302 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +/* Authorization scheme */ +case 146: +YY_RULE_SETUP +#line 305 "scanner.lxx" +{ return T_AUTH_DIGEST; } + YY_BREAK +case 147: +YY_RULE_SETUP +#line 306 "scanner.lxx" +{ yylval.yyt_str = new string(yytext); + MEMMAN_NEW(yylval.yyt_str); + return T_AUTH_OTHER; } + YY_BREAK +case 148: +YY_RULE_SETUP +#line 309 "scanner.lxx" +/* Skip white space */ + YY_BREAK +case 149: +YY_RULE_SETUP +#line 310 "scanner.lxx" +{ return T_ERROR; } + YY_BREAK +case 150: +/* rule 150 can match eol */ +YY_RULE_SETUP +#line 311 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 151: +/* rule 151 can match eol */ +YY_RULE_SETUP +#line 312 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +/* IPv6 address + * NOTE: the validity of the format is not checked here. + */ +case 152: +YY_RULE_SETUP +#line 317 "scanner.lxx" +{ yylval.yyt_str = new string(yytext); + MEMMAN_NEW(yylval.yyt_str); + return T_IPV6ADDR; } + YY_BREAK +case 153: +YY_RULE_SETUP +#line 320 "scanner.lxx" +/* Skip white space */ + YY_BREAK +case 154: +YY_RULE_SETUP +#line 321 "scanner.lxx" +{ return T_ERROR; } + YY_BREAK +case 155: +/* rule 155 can match eol */ +YY_RULE_SETUP +#line 322 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 156: +/* rule 156 can match eol */ +YY_RULE_SETUP +#line 323 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +/* Parameter values may contain an IPv6 address or reference. */ +case 157: +YY_RULE_SETUP +#line 326 "scanner.lxx" +{ yylval.yyt_str = new string(yytext); + MEMMAN_NEW(yylval.yyt_str); + return T_PARAMVAL; } + YY_BREAK +case 158: +YY_RULE_SETUP +#line 329 "scanner.lxx" +{ yy_push_state(C_QSTRING); } + YY_BREAK +case 159: +YY_RULE_SETUP +#line 330 "scanner.lxx" +/* Skip white space */ + YY_BREAK +case 160: +YY_RULE_SETUP +#line 331 "scanner.lxx" +{ return T_ERROR; } + YY_BREAK +case 161: +/* rule 161 can match eol */ +YY_RULE_SETUP +#line 332 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 162: +/* rule 162 can match eol */ +YY_RULE_SETUP +#line 333 "scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 163: +YY_RULE_SETUP +#line 334 "scanner.lxx" +ECHO; + YY_BREAK +#line 2482 "scanner.cxx" + case YY_STATE_EOF(INITIAL): + case YY_STATE_EOF(C_URI): + case YY_STATE_EOF(C_URI_SPECIAL): + case YY_STATE_EOF(C_QSTRING): + case YY_STATE_EOF(C_LANG): + case YY_STATE_EOF(C_WORD): + case YY_STATE_EOF(C_NUM): + case YY_STATE_EOF(C_DATE): + case YY_STATE_EOF(C_LINE): + case YY_STATE_EOF(C_COMMENT): + case YY_STATE_EOF(C_NEW): + case YY_STATE_EOF(C_AUTH_SCHEME): + case YY_STATE_EOF(C_RPAREN): + case YY_STATE_EOF(C_IPV6ADDR): + case YY_STATE_EOF(C_PARAMVAL): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = (yy_hold_char); + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state ); + + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++(yy_c_buf_p); + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = (yy_c_buf_p); + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_END_OF_FILE: + { + (yy_did_buffer_switch_on_eof) = 0; + + if ( yywrap( ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = + (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + (yy_c_buf_p) = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)]; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ +} /* end of yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (void) +{ + register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + register char *source = (yytext_ptr); + register int number_to_move, i; + int ret_val; + + if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr)) - 1; + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + YY_FATAL_ERROR( +"input buffer overflow, can't enlarge buffer because scanner uses REJECT" ); + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + (yy_n_chars), num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + if ( (yy_n_chars) == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart(yyin ); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + (yy_n_chars) += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR; + + (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (void) +{ + register yy_state_type yy_current_state; + register char *yy_cp; + + yy_current_state = (yy_start); + yy_current_state += YY_AT_BOL(); + + (yy_state_ptr) = (yy_state_buf); + *(yy_state_ptr)++ = yy_current_state; + + for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) + { + register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 714 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + *(yy_state_ptr)++ = yy_current_state; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state ) +{ + register int yy_is_jam; + + register YY_CHAR yy_c = 1; + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 714 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + yy_is_jam = (yy_current_state == 713); + if ( ! yy_is_jam ) + *(yy_state_ptr)++ = yy_current_state; + + return yy_is_jam ? 0 : yy_current_state; +} + + static void yyunput (int c, register char * yy_bp ) +{ + register char *yy_cp; + + yy_cp = (yy_c_buf_p); + + /* undo effects of setting up yytext */ + *yy_cp = (yy_hold_char); + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + { /* need to shift things up to make room */ + /* +2 for EOB chars. */ + register int number_to_move = (yy_n_chars) + 2; + register char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2]; + register char *source = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]; + + while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + *--dest = *--source; + + yy_cp += (int) (dest - source); + yy_bp += (int) (dest - source); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_buf_size; + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + YY_FATAL_ERROR( "flex scanner push-back overflow" ); + } + + *--yy_cp = (char) c; + + (yytext_ptr) = yy_bp; + (yy_hold_char) = *yy_cp; + (yy_c_buf_p) = yy_cp; +} + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (void) +#else + static int input (void) +#endif + +{ + int c; + + *(yy_c_buf_p) = (yy_hold_char); + + if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + /* This was really a NUL. */ + *(yy_c_buf_p) = '\0'; + + else + { /* need more input */ + int offset = (yy_c_buf_p) - (yytext_ptr); + ++(yy_c_buf_p); + + switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart(yyin ); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap( ) ) + return EOF; + + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(); +#else + return input(); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = (yytext_ptr) + offset; + break; + } + } + } + + c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */ + *(yy_c_buf_p) = '\0'; /* preserve yytext */ + (yy_hold_char) = *++(yy_c_buf_p); + + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = (c == '\n'); + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * + * @note This function does not reset the start condition to @c INITIAL . + */ + void yyrestart (FILE * input_file ) +{ + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer(yyin,YY_BUF_SIZE ); + } + + yy_init_buffer(YY_CURRENT_BUFFER,input_file ); + yy_load_buffer_state( ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * + */ + void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ) +{ + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state( ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + (yy_did_buffer_switch_on_eof) = 1; +} + +static void yy_load_buffer_state (void) +{ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + (yy_hold_char) = *(yy_c_buf_p); +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * + * @return the allocated buffer state. + */ + YY_BUFFER_STATE yy_create_buffer (FILE * file, int size ) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc(sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yyalloc(b->yy_buf_size + 2 ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer(b,file ); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * + */ + void yy_delete_buffer (YY_BUFFER_STATE b ) +{ + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yyfree((void *) b->yy_ch_buf ); + + yyfree((void *) b ); +} + +#ifndef __cplusplus +extern int isatty (int ); +#endif /* __cplusplus */ + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file ) + +{ + int oerrno = errno; + + yy_flush_buffer(b ); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * + */ + void yy_flush_buffer (YY_BUFFER_STATE b ) +{ + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yy_load_buffer_state( ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * + */ +void yypush_buffer_state (YY_BUFFER_STATE new_buffer ) +{ + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + (yy_buffer_stack_top)++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * + */ +void yypop_buffer_state (void) +{ + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + if ((yy_buffer_stack_top) > 0) + --(yy_buffer_stack_top); + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void yyensure_buffer_stack (void) +{ + int num_to_alloc; + + if (!(yy_buffer_stack)) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; + (yy_buffer_stack) = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + ); + + memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + (yy_buffer_stack_max) = num_to_alloc; + (yy_buffer_stack_top) = 0; + return; + } + + if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + int grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = (yy_buffer_stack_max) + grow_size; + (yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc + ((yy_buffer_stack), + num_to_alloc * sizeof(struct yy_buffer_state*) + ); + + /* zero only the new slots.*/ + memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*)); + (yy_buffer_stack_max) = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size ) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return 0; + + b = (YY_BUFFER_STATE) yyalloc(sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = 0; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer(b ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to yylex() will + * scan from a @e copy of @a str. + * @param str a NUL-terminated string to scan + * + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * yy_scan_bytes() instead. + */ +YY_BUFFER_STATE yy_scan_string (yyconst char * yystr ) +{ + + return yy_scan_bytes(yystr,strlen(yystr) ); +} + +/** Setup the input buffer state to scan the given bytes. The next call to yylex() will + * scan from a @e copy of @a bytes. + * @param bytes the byte buffer to scan + * @param len the number of bytes in the buffer pointed to by @a bytes. + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_bytes (yyconst char * yybytes, int _yybytes_len ) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = _yybytes_len + 2; + buf = (char *) yyalloc(n ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer(buf,n ); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + + static void yy_push_state (int new_state ) +{ + if ( (yy_start_stack_ptr) >= (yy_start_stack_depth) ) + { + yy_size_t new_size; + + (yy_start_stack_depth) += YY_START_STACK_INCR; + new_size = (yy_start_stack_depth) * sizeof( int ); + + if ( ! (yy_start_stack) ) + (yy_start_stack) = (int *) yyalloc(new_size ); + + else + (yy_start_stack) = (int *) yyrealloc((void *) (yy_start_stack),new_size ); + + if ( ! (yy_start_stack) ) + YY_FATAL_ERROR( + "out of memory expanding start-condition stack" ); + } + + (yy_start_stack)[(yy_start_stack_ptr)++] = YY_START; + + BEGIN(new_state); +} + + static void yy_pop_state (void) +{ + if ( --(yy_start_stack_ptr) < 0 ) + YY_FATAL_ERROR( "start-condition stack underflow" ); + + BEGIN((yy_start_stack)[(yy_start_stack_ptr)]); +} + + static int yy_top_state (void) +{ + return (yy_start_stack)[(yy_start_stack_ptr) - 1]; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yy_fatal_error (yyconst char* msg ) +{ + (void) fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = (yy_hold_char); \ + (yy_c_buf_p) = yytext + yyless_macro_arg; \ + (yy_hold_char) = *(yy_c_buf_p); \ + *(yy_c_buf_p) = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the input stream. + * + */ +FILE *yyget_in (void) +{ + return yyin; +} + +/** Get the output stream. + * + */ +FILE *yyget_out (void) +{ + return yyout; +} + +/** Get the length of the current token. + * + */ +int yyget_leng (void) +{ + return yyleng; +} + +/** Get the current token. + * + */ + +char *yyget_text (void) +{ + return yytext; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param in_str A readable stream. + * + * @see yy_switch_to_buffer + */ +void yyset_in (FILE * in_str ) +{ + yyin = in_str ; +} + +void yyset_out (FILE * out_str ) +{ + yyout = out_str ; +} + +int yyget_debug (void) +{ + return yy_flex_debug; +} + +void yyset_debug (int bdebug ) +{ + yy_flex_debug = bdebug ; +} + +static int yy_init_globals (void) +{ + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from yylex_destroy(), so don't allocate here. + */ + + (yy_buffer_stack) = 0; + (yy_buffer_stack_top) = 0; + (yy_buffer_stack_max) = 0; + (yy_c_buf_p) = (char *) 0; + (yy_init) = 0; + (yy_start) = 0; + + (yy_start_stack_ptr) = 0; + (yy_start_stack_depth) = 0; + (yy_start_stack) = NULL; + + (yy_state_buf) = 0; + (yy_state_ptr) = 0; + (yy_full_match) = 0; + (yy_lp) = 0; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = (FILE *) 0; + yyout = (FILE *) 0; +#endif + + /* For future reference: Set errno on error, since we are called by + * yylex_init() + */ + return 0; +} + +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +int yylex_destroy (void) +{ + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yy_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yypop_buffer_state(); + } + + /* Destroy the stack itself. */ + yyfree((yy_buffer_stack) ); + (yy_buffer_stack) = NULL; + + /* Destroy the start condition stack. */ + yyfree((yy_start_stack) ); + (yy_start_stack) = NULL; + + yyfree ( (yy_state_buf) ); + (yy_state_buf) = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * yylex() is called, initialization will occur. */ + yy_init_globals( ); + + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, yyconst char * s2, int n ) +{ + register int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * s ) +{ + register int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *yyalloc (yy_size_t size ) +{ + return (void *) malloc( size ); +} + +void *yyrealloc (void * ptr, yy_size_t size ) +{ + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return (void *) realloc( (char *) ptr, size ); +} + +void yyfree (void * ptr ) +{ + free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#line 334 "scanner.lxx" diff --git a/src/parser/scanner.lxx b/src/parser/scanner.lxx new file mode 100644 index 0000000..05ffb97 --- /dev/null +++ b/src/parser/scanner.lxx @@ -0,0 +1,333 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +%{ +#include +#include +#include +#include +#include "parse_ctrl.h" +#include "parser.h" +#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 */ +Digest { 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; } diff --git a/src/parser/sip_body.cpp b/src/parser/sip_body.cpp new file mode 100644 index 0000000..cc95f85 --- /dev/null +++ b/src/parser/sip_body.cpp @@ -0,0 +1,326 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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 (!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(); +} diff --git a/src/parser/sip_body.h b/src/parser/sip_body.h new file mode 100644 index 0000000..88c93a7 --- /dev/null +++ b/src/parser/sip_body.h @@ -0,0 +1,260 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/parser/sip_message.cpp b/src/parser/sip_message.cpp new file mode 100644 index 0000000..85cda18 --- /dev/null +++ b/src/parser/sip_message.cpp @@ -0,0 +1,476 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include + +#include "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_; +} diff --git a/src/parser/sip_message.h b/src/parser/sip_message.h new file mode 100644 index 0000000..6602438 --- /dev/null +++ b/src/parser/sip_message.h @@ -0,0 +1,270 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/patterns/Makefile.am b/src/patterns/Makefile.am new file mode 100644 index 0000000..4dc022e --- /dev/null +++ b/src/patterns/Makefile.am @@ -0,0 +1,7 @@ +AM_CPPFLAGS = -Wall -I$(top_srcdir)/src + +noinst_LIBRARIES = libpatterns.a + +libpatterns_a_SOURCES =\ + observer.cpp\ + observer.h diff --git a/src/patterns/observer.cpp b/src/patterns/observer.cpp new file mode 100644 index 0000000..c09e80c --- /dev/null +++ b/src/patterns/observer.cpp @@ -0,0 +1,53 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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(); +} diff --git a/src/patterns/observer.h b/src/patterns/observer.h new file mode 100644 index 0000000..9aefaef --- /dev/null +++ b/src/patterns/observer.h @@ -0,0 +1,83 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/phone.cpp b/src/phone.cpp new file mode 100644 index 0000000..96882c6 --- /dev/null +++ b/src/phone.cpp @@ -0,0 +1,3562 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include "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) { + // 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) { + // 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) { + // 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()); + + lock(); + + // 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(); + } + } + + unlock(); +} + +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) +{ + // 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) { + // Ignore if active line is idle + if (lines[active_line]->get_state() == LS_IDLE) return; + + lines[active_line]->answer(); +} + +void t_phone::reject(void) { + // 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) { + 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) +{ + // 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) { + // 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 { + // 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) { + 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; + } + + return lines[active_line]->hold(rtponly); +} + +void t_phone::retrieve(void) { + lines[active_line]->retrieve(); +} + +void t_phone::refer(const t_url &uri, const string &display) { + lines[active_line]->refer(uri, display); +} + +void t_phone::refer(unsigned short lineno_from, unsigned short lineno_to) +{ + // 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_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_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); + + 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); + + // 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) { + 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) { + 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) { + 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) { + 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) { + // 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + // 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; + + 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"); + + 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. + + 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); + } + } + + // 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; + + // 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; + + 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; + + 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; + + 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; + + // 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; + + 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; + } + + 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 + 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; + + 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(); + + 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; + + 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) { + 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; + + lock(); + + 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; + } + + unlock(); +} + +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) +{ + 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) { + 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) { + lock(); + + 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); + } + + unlock(); +} + +void t_phone::handle_broken_connection(t_event_broken_connection *e) { + // 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]; + } + + // 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) +{ + lock(); + + 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(); + } + + unlock(); +} + +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) +{ + lock(); + + 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(); + } + + unlock(); +} + +void t_phone::pub_options(t_user *user, + const t_url &to_uri, const string &to_display) +{ + lock(); + + 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(); + } + + unlock(); +} + +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 + { + 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; + + lock(); + retval = lines[active_line]->seize(); + unlock(); + + return retval; +} + +bool t_phone::pub_seize(unsigned short line) { + assert(line < NUM_USER_LINES); + bool retval; + + lock(); + retval = lines[line]->seize(); + unlock(); + + return retval; +} + +void t_phone::pub_unseize(void) { + lock(); + lines[active_line]->unseize(); + unlock(); +} + +void t_phone::pub_unseize(unsigned short line) { + assert(line < NUM_USER_LINES); + + lock(); + lines[line]->unseize(); + unlock(); +} + +void t_phone::pub_confirm_zrtp_sas(unsigned short line) { + assert(line < NUM_USER_LINES); + lock(); + lines[line]->confirm_zrtp_sas(); + unlock(); +} + +void t_phone::pub_confirm_zrtp_sas(void) { + lock(); + lines[active_line]->confirm_zrtp_sas(); + unlock(); +} + +void t_phone::pub_reset_zrtp_sas_confirmation(unsigned short line) { + assert(line < NUM_USER_LINES); + lock(); + lines[line]->reset_zrtp_sas_confirmation(); + unlock(); +} + +void t_phone::pub_reset_zrtp_sas_confirmation(void) { + lock(); + lines[active_line]->reset_zrtp_sas_confirmation(); + unlock(); +} + +void t_phone::pub_enable_zrtp(void) { + lock(); + lines[active_line]->enable_zrtp(); + unlock(); +} + +void t_phone::pub_zrtp_request_go_clear(void) { + lock(); + lines[active_line]->zrtp_request_go_clear(); + unlock(); +} + +void t_phone::pub_zrtp_go_clear_ok(unsigned short line) { + assert(line < NUM_USER_LINES); + lock(); + lines[line]->zrtp_go_clear_ok(); + unlock(); +} + +void t_phone::pub_subscribe_mwi(t_user *user) { + lock(); + + 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(); + } + + unlock(); +} + +void t_phone::pub_unsubscribe_mwi(t_user *user) { + lock(); + + 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(); + } + + unlock(); +} + +void t_phone::pub_subscribe_presence(t_user *user) { + lock(); + + 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(); + } + + unlock(); +} + +void t_phone::pub_unsubscribe_presence(t_user *user) { + lock(); + + 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(); + } + + unlock(); +} + +void t_phone::pub_publish_presence(t_user *user, t_presence_state::t_basic_state basic_state) { + lock(); + + 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(); + } + + unlock(); +} + +void t_phone::pub_unpublish_presence(t_user *user) { + lock(); + + 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(); + } + + unlock(); +} + +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; + + lock(); + + 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; + } + + unlock(); + + 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; + + lock(); + + 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; + } + + unlock(); + + return retval; +} + +t_phone_state t_phone::get_state(void) const { + lock(); + for (unsigned short i = 0; i < NUM_USER_LINES; i++) { + if (lines[i]->get_state() == LS_IDLE) { + unlock(); + return PS_IDLE; + } + } + + // All lines are busy, so the phone is busy. + unlock(); + return PS_BUSY; +} + +bool t_phone::all_lines_idle(void) const { + lock(); + for (unsigned short i = 0; i < NUM_USER_LINES; i++) { + if (lines[i]->get_substate() != LSSUB_IDLE) { + unlock(); + return false; + } + } + + // All lines are idle + unlock(); + return true; +} + +bool t_phone::get_idle_line(unsigned short &lineno) const { + lock(); + + 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; + } + } + + unlock(); + return found_idle_line; +} + +void t_phone::set_active_line(unsigned short l) { + lock(); + assert (l < NUM_USER_LINES); + active_line = l; + unlock(); +} + +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 { + 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; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) result = pu->authorize(r, resp); + unlock(); + + return result; +} + +void t_phone::remove_cached_credentials(t_user *user, const string &realm) { + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) pu->remove_cached_credentials(realm); + unlock(); +} + +bool t_phone::get_is_registered(t_user *user) { + bool result = false; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) result = pu->get_is_registered(); + unlock(); + + return result; +} + +bool t_phone::get_last_reg_failed(t_user *user) { + bool result = false; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) result = pu->get_last_reg_failed(); + unlock(); + + return result; +} + +t_line_state t_phone::get_line_state(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + t_line_state s = get_line(lineno)->get_state(); + unlock(); + return s; +} + +t_line_substate t_phone::get_line_substate(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + t_line_substate s = get_line(lineno)->get_substate(); + unlock(); + return s; +} + +bool t_phone::is_line_on_hold(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + bool b = get_line(lineno)->get_is_on_hold(); + unlock(); + return b; +} + +bool t_phone::is_line_muted(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + bool b = get_line(lineno)->get_is_muted(); + unlock(); + return b; +} + +bool t_phone::is_line_transfer_consult(unsigned short lineno, + unsigned short &transfer_from_line) const +{ + assert(lineno < lines.size()); + + lock(); + bool b = get_line(lineno)->get_is_transfer_consult(transfer_from_line); + unlock(); + return b; +} + +bool t_phone::line_to_be_transferred(unsigned short lineno, + unsigned short &transfer_to_line) const +{ + assert(lineno < lines.size()); + + lock(); + bool b = get_line(lineno)->get_to_be_transferred(transfer_to_line); + unlock(); + return b; +} + +bool t_phone::is_line_encrypted(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + bool b = get_line(lineno)->get_is_encrypted(); + unlock(); + return b; +} + +bool t_phone::is_line_auto_answered(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + bool b = get_line(lineno)->get_auto_answer(); + unlock(); + return b; +} + +t_refer_state t_phone::get_line_refer_state(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + t_refer_state s = get_line(lineno)->get_refer_state(); + unlock(); + return s; +} + +t_user *t_phone::get_line_user(unsigned short lineno) { + assert(lineno < lines.size()); + lock(); + t_user *user = get_line(lineno)->get_user(); + unlock(); + return user; +} + +bool t_phone::has_line_media(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + bool b = get_line(lineno)->has_media(); + unlock(); + return b; +} + +bool t_phone::is_mwi_subscribed(t_user *user) const { + bool result = false; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) result = pu->is_mwi_subscribed(); + unlock(); + + return result; +} + +bool t_phone::is_mwi_terminated(t_user *user) const { + bool result = false; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) result = pu->is_mwi_terminated(); + unlock(); + + return result; +} + +t_mwi t_phone::get_mwi(t_user *user) const { + t_mwi result; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) result = pu->mwi; + unlock(); + + return result; +} + +bool t_phone::is_presence_terminated(t_user *user) const { + bool result = false; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) result = pu->is_presence_terminated(); + unlock(); + + return result; +} + +t_url t_phone::get_remote_uri(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + t_url uri = get_line(lineno)->get_remote_uri(); + unlock(); + return uri; +} + +string t_phone::get_remote_display(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + string display = get_line(lineno)->get_remote_display(); + unlock(); + return display; +} + +bool t_phone::part_of_3way(unsigned short lineno) { + lock(); + + if (!is_3way) { + unlock(); + return false; + } + + if (line1_3way->get_line_number() == lineno) { + unlock(); + return true; + } + + if (line2_3way->get_line_number() == lineno) { + unlock(); + return true; + } + + unlock(); + return false; +} + +t_line *t_phone::get_3way_peer_line(unsigned short lineno) { + lock(); + + if (!is_3way) { + unlock(); + return NULL; + } + + if (line1_3way->get_line_number() == lineno) { + unlock(); + return line2_3way; + } + + unlock(); + return line1_3way; +} + +bool t_phone::join_3way(unsigned short lineno1, unsigned short lineno2) { + assert(lineno1 < NUM_USER_LINES); + assert(lineno2 < NUM_USER_LINES); + + lock(); + + // Check if there isn't a 3-way already + if (is_3way) { + unlock(); + return false; + } + + // Both lines must have a call. + if (lines[lineno1]->get_substate() != LSSUB_ESTABLISHED || + lines[lineno2]->get_substate() != LSSUB_ESTABLISHED) + { + unlock(); + 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 { + unlock(); + 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(); + + unlock(); + return true; +} + +void t_phone::notify_refer_progress(t_response *r, unsigned short referee_lineno) { + 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()); + + lock(); + t_call_info call_info = get_line(lineno)->get_call_info(); + unlock(); + return call_info; +} + +t_call_record t_phone::get_call_hist(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + t_call_record call_hist = get_line(lineno)->call_hist_record; + unlock(); + return call_hist; +} + +string t_phone::get_ringtone(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + string ringtone = get_line(lineno)->get_ringtone(); + unlock(); + return ringtone; +} + +time_t t_phone::get_startup_time(void) const { + return startup_time; +} + +void t_phone::init_rtp_ports(void) { + 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) { + lock(); + + t_phone_user *existing_phone_user = NULL; + + 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; + unlock(); + 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; + unlock(); + return false; + } + } + + // Activate existing profile + if (existing_phone_user) { + if (!existing_phone_user->is_active()) { + existing_phone_user->activate(user_config); + } + unlock(); + return true; + } + + // Add the user + t_phone_user *pu = new t_phone_user(user_config); + MEMMAN_NEW(pu); + phone_users.push_back(pu); + unlock(); + + return true; +} + +void t_phone::remove_phone_user(const t_user &user_config) { + lock(); + t_phone_user *pu = find_phone_user(user_config.get_profile_name()); + if (pu) pu->deactivate(); + unlock(); +} + +list t_phone::ref_users(void) { + list l; + + lock(); + for (list::iterator i = phone_users.begin(); + i != phone_users.end(); i++) + { + if (!(*i)->is_active()) continue; + l.push_back((*i)->get_user_profile()); + } + unlock(); + + return l; +} + +t_user *t_phone::ref_user_display_uri(const string &display_uri) { + t_user *u = NULL; + + lock(); + 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; + } + } + unlock(); + + return u; +} + +t_user *t_phone::ref_user_profile(const string &profile_name) { + t_user *u = NULL; + + lock(); + t_phone_user *pu = find_phone_user(profile_name); + if (pu) u = pu->get_user_profile(); + unlock(); + + return u; +} + +t_service *t_phone::ref_service(t_user *user) { + assert(user); + t_service *srv = NULL; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) srv = pu->service; + unlock(); + + return srv; +} + +t_buddy_list *t_phone::ref_buddy_list(t_user *user) { + assert(user); + t_buddy_list *l = NULL; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) l = pu->get_buddy_list(); + unlock(); + + return l; +} + +t_presence_epa *t_phone::ref_presence_epa(t_user *user) { + assert(user); + t_presence_epa *epa = NULL; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) epa = pu->get_presence_epa(); + unlock(); + + return epa; +} + +string t_phone::get_ip_sip(const t_user *user, const string &auto_ip) const { + string result; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + result = pu->get_ip_sip(auto_ip); + } else { + result = LOCAL_IP; + } + unlock(); + + 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; + + lock(); + 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(); + } + unlock(); + + return result; +} + +bool t_phone::use_stun(t_user *user) { + bool result; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + result = pu->use_stun; + } else { + result = false; + } + unlock(); + + return result; +} + +bool t_phone::use_nat_keepalive(t_user *user) { + bool result; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + result = pu->use_nat_keepalive; + } else { + result = false; + } + unlock(); + + return result; +} + +void t_phone::disable_stun(t_user *user) { + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) pu->use_stun = false; + unlock(); +} + +void t_phone::sync_nat_keepalive(t_user *user) { + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) pu->sync_nat_keepalive(); + unlock(); +} + +bool t_phone::stun_discover_nat(list &msg_list) { + bool retval = true; + + lock(); + 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(); + } + } + } + unlock(); + + return retval; +} + +bool t_phone::stun_discover_nat(t_user *user, string &msg) { + bool retval = true; + + lock(); + 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(); + } + } + unlock(); + + return retval; +} + +t_response *t_phone::create_options_response(t_user *user, t_request *r, + bool in_dialog) +{ + t_response *resp; + + lock(); + 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); + } + unlock(); + + return resp; +} + +void t_phone::init(void) { + lock(); + + 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.) + } + + unlock(); +} + +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; + lock(); + + // 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; + } + } + + // 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); + } + } + + unlock(); + + // 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 + lock(); + 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(); + } + } + + log_file->write_report("Finished phone termination.", + "t_phone::terminate", LOG_NORMAL, LOG_DEBUG); + unlock(); +} + +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; + } +} diff --git a/src/phone.h b/src/phone.h new file mode 100644 index 0000000..2a08936 --- /dev/null +++ b/src/phone.h @@ -0,0 +1,707 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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; + + // 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; + + // 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 + + // 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 succesfully. + * @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 succesful, 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 diff --git a/src/phone_user.cpp b/src/phone_user.cpp new file mode 100644 index 0000000..630ca69 --- /dev/null +++ b/src/phone_user.cpp @@ -0,0 +1,1653 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/phone_user.h b/src/phone_user.h new file mode 100644 index 0000000..387baba --- /dev/null +++ b/src/phone_user.h @@ -0,0 +1,491 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 succesful 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 diff --git a/src/presence/Makefile.am b/src/presence/Makefile.am new file mode 100644 index 0000000..17caf93 --- /dev/null +++ b/src/presence/Makefile.am @@ -0,0 +1,17 @@ +AM_CPPFLAGS = -Wall $(CCRTP_CFLAGS) $(XML2_CFLAGS) -I$(top_srcdir)/src + +noinst_LIBRARIES = libpresence.a + +libpresence_a_SOURCES =\ + buddy.cpp\ + pidf_body.cpp\ + presence_dialog.cpp\ + presence_epa.cpp\ + presence_state.cpp\ + presence_subscription.cpp\ + buddy.h\ + pidf_body.h\ + presence_dialog.h\ + presence_epa.h\ + presence_state.h\ + presence_subscription.h diff --git a/src/presence/buddy.cpp b/src/presence/buddy.cpp new file mode 100644 index 0000000..075626a --- /dev/null +++ b/src/presence/buddy.cpp @@ -0,0 +1,574 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/presence/buddy.h b/src/presence/buddy.h new file mode 100644 index 0000000..b7852de --- /dev/null +++ b/src/presence/buddy.h @@ -0,0 +1,341 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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, succesful 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, succesful 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 diff --git a/src/presence/pidf_body.cpp b/src/presence/pidf_body.cpp new file mode 100644 index 0000000..aa67401 --- /dev/null +++ b/src/presence/pidf_body.cpp @@ -0,0 +1,221 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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); +} diff --git a/src/presence/pidf_body.h b/src/presence/pidf_body.h new file mode 100644 index 0000000..445d531 --- /dev/null +++ b/src/presence/pidf_body.h @@ -0,0 +1,104 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/presence/presence_dialog.cpp b/src/presence/presence_dialog.cpp new file mode 100644 index 0000000..f281493 --- /dev/null +++ b/src/presence/presence_dialog.cpp @@ -0,0 +1,35 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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); +} diff --git a/src/presence/presence_dialog.h b/src/presence/presence_dialog.h new file mode 100644 index 0000000..10513bd --- /dev/null +++ b/src/presence/presence_dialog.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/presence/presence_epa.cpp b/src/presence/presence_epa.cpp new file mode 100644 index 0000000..0d4071f --- /dev/null +++ b/src/presence/presence_epa.cpp @@ -0,0 +1,71 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/presence/presence_epa.h b/src/presence/presence_epa.h new file mode 100644 index 0000000..f224426 --- /dev/null +++ b/src/presence/presence_epa.h @@ -0,0 +1,67 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/presence/presence_state.cpp b/src/presence/presence_state.cpp new file mode 100644 index 0000000..91c9ba8 --- /dev/null +++ b/src/presence/presence_state.cpp @@ -0,0 +1,100 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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(); +} diff --git a/src/presence/presence_state.h b/src/presence/presence_state.h new file mode 100644 index 0000000..f86082e --- /dev/null +++ b/src/presence/presence_state.h @@ -0,0 +1,94 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/presence/presence_subscription.cpp b/src/presence/presence_subscription.cpp new file mode 100644 index 0000000..af1514e --- /dev/null +++ b/src/presence/presence_subscription.cpp @@ -0,0 +1,144 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/presence/presence_subscription.h b/src/presence/presence_subscription.h new file mode 100644 index 0000000..09fb6ba --- /dev/null +++ b/src/presence/presence_subscription.h @@ -0,0 +1,51 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/prohibit_thread.cpp b/src/prohibit_thread.cpp new file mode 100644 index 0000000..6fdfb13 --- /dev/null +++ b/src/prohibit_thread.cpp @@ -0,0 +1,39 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/prohibit_thread.h b/src/prohibit_thread.h new file mode 100644 index 0000000..72b139a --- /dev/null +++ b/src/prohibit_thread.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/protocol.h b/src/protocol.h new file mode 100644 index 0000000..a400a6e --- /dev/null +++ b/src/protocol.h @@ -0,0 +1,399 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/redirect.cpp b/src/redirect.cpp new file mode 100644 index 0000000..00346e6 --- /dev/null +++ b/src/redirect.cpp @@ -0,0 +1,70 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include "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; + } + } +} diff --git a/src/redirect.h b/src/redirect.h new file mode 100644 index 0000000..b2dd50a --- /dev/null +++ b/src/redirect.h @@ -0,0 +1,67 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/sdp/Makefile.am b/src/sdp/Makefile.am new file mode 100644 index 0000000..ea3340c --- /dev/null +++ b/src/sdp/Makefile.am @@ -0,0 +1,27 @@ +AM_CPPFLAGS = \ + -Wall \ + -I$(top_srcdir)/src\ + $(XML2_CFLAGS) + +AM_YFLAGS = -p yysdp -d +AM_LFLAGS = -Pyysdp -olex.yy.c -i + +# This target is only for testing the parser in isolation +# noinst_PROGRAMS = sdpparse + +# sdpparse_SOURCES = main.cpp + +# sdpparse_LDADD = $(top_builddir)/src/util.o\ +# $(top_builddir)/src/sdp/libsdpparser.a\ +# $(top_builddir)/src/parser/sip_body.o + +noinst_LIBRARIES = libsdpparser.a + + +libsdpparser_a_SOURCES =\ + sdp.cpp\ + sdp_parse_ctrl.cpp\ + sdp_parser.yxx\ + sdp_scanner.lxx\ + sdp.h\ + sdp_parse_ctrl.h diff --git a/src/sdp/sdp.cpp b/src/sdp/sdp.cpp new file mode 100644 index 0000000..7d5de3b --- /dev/null +++ b/src/sdp/sdp.cpp @@ -0,0 +1,806 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include "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); + } +} + +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); + } +} + +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); + } +} + +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); + } +} + +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_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); + } +} + +/////////////////////////////////// +// 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)); + } +} + +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_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; +} diff --git a/src/sdp/sdp.h b/src/sdp/sdp.h new file mode 100644 index 0000000..92edebe --- /dev/null +++ b/src/sdp/sdp.h @@ -0,0 +1,295 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 + +// 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_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_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 diff --git a/src/sdp/sdp_parse_ctrl.cpp b/src/sdp/sdp_parse_ctrl.cpp new file mode 100644 index 0000000..44d0886 --- /dev/null +++ b/src/sdp/sdp_parse_ctrl.cpp @@ -0,0 +1,70 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/sdp/sdp_parse_ctrl.h b/src/sdp/sdp_parse_ctrl.h new file mode 100644 index 0000000..aff1481 --- /dev/null +++ b/src/sdp/sdp_parse_ctrl.h @@ -0,0 +1,63 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/sdp/sdp_parser.h b/src/sdp/sdp_parser.h new file mode 100644 index 0000000..40c0bb8 --- /dev/null +++ b/src/sdp/sdp_parser.h @@ -0,0 +1,98 @@ +/* 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* 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; + diff --git a/src/sdp/sdp_parser.yxx b/src/sdp/sdp_parser.yxx new file mode 100644 index 0000000..5926f09 --- /dev/null +++ b/src/sdp/sdp_parser.yxx @@ -0,0 +1,294 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +%{ +#include +#include +#include +#include "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); +} diff --git a/src/sdp/sdp_scanner.cxx b/src/sdp/sdp_scanner.cxx new file mode 100644 index 0000000..c8f1202 --- /dev/null +++ b/src/sdp/sdp_scanner.cxx @@ -0,0 +1,1953 @@ +#line 2 "sdp_scanner.cxx" + +#line 4 "sdp_scanner.cxx" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 5 +#define YY_FLEX_SUBMINOR_VERSION 33 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; +#endif /* ! C99 */ + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#endif /* ! FLEXINT_H */ + +#ifdef __cplusplus + +/* The "const" storage-class-modifier is valid. */ +#define YY_USE_CONST + +#else /* ! __cplusplus */ + +#if __STDC__ + +#define YY_USE_CONST + +#endif /* __STDC__ */ +#endif /* ! __cplusplus */ + +#ifdef YY_USE_CONST +#define yyconst const +#else +#define yyconst +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an unsigned + * integer for use as an array index. If the signed char is negative, + * we want to instead treat it as an 8-bit unsigned char, hence the + * double cast. + */ +#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN (yy_start) = 1 + 2 * + +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START (((yy_start) - 1) / 2) +#define YYSTATE YY_START + +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) + +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yysdprestart(yysdpin ) + +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#define YY_BUF_SIZE 16384 +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +extern int yysdpleng; + +extern FILE *yysdpin, *yysdpout; + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yysdptext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = (yy_hold_char); \ + YY_RESTORE_YY_MORE_OFFSET \ + (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yysdptext again */ \ + } \ + while ( 0 ) + +#define unput(c) yyunput( c, (yytext_ptr) ) + +/* The following is because we cannot portably get our hands on size_t + * (without autoconf's help, which isn't available because we want + * flex-generated scanners to compile on their own). + */ + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef unsigned int yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + yy_size_t yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yysdprestart()), so that the user can continue scanning by + * just pointing yysdpin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* Stack of input buffers. */ +static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */ +static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */ +static YY_BUFFER_STATE * yy_buffer_stack = 0; /**< Stack as an array. */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \ + ? (yy_buffer_stack)[(yy_buffer_stack_top)] \ + : NULL) + +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)] + +/* yy_hold_char holds the character lost when yysdptext is formed. */ +static char yy_hold_char; +static int yy_n_chars; /* number of characters read into yy_ch_buf */ +int yysdpleng; + +/* Points to current character in buffer. */ +static char *yy_c_buf_p = (char *) 0; +static int yy_init = 0; /* whether we need to initialize */ +static int yy_start = 0; /* start state number */ + +/* Flag which is used to allow yysdpwrap()'s to do buffer switches + * instead of setting up a fresh yysdpin. A bit of a hack ... + */ +static int yy_did_buffer_switch_on_eof; + +void yysdprestart (FILE *input_file ); +void yysdp_switch_to_buffer (YY_BUFFER_STATE new_buffer ); +YY_BUFFER_STATE yysdp_create_buffer (FILE *file,int size ); +void yysdp_delete_buffer (YY_BUFFER_STATE b ); +void yysdp_flush_buffer (YY_BUFFER_STATE b ); +void yysdppush_buffer_state (YY_BUFFER_STATE new_buffer ); +void yysdppop_buffer_state (void ); + +static void yysdpensure_buffer_stack (void ); +static void yysdp_load_buffer_state (void ); +static void yysdp_init_buffer (YY_BUFFER_STATE b,FILE *file ); + +#define YY_FLUSH_BUFFER yysdp_flush_buffer(YY_CURRENT_BUFFER ) + +YY_BUFFER_STATE yysdp_scan_buffer (char *base,yy_size_t size ); +YY_BUFFER_STATE yysdp_scan_string (yyconst char *yy_str ); +YY_BUFFER_STATE yysdp_scan_bytes (yyconst char *bytes,int len ); + +void *yysdpalloc (yy_size_t ); +void *yysdprealloc (void *,yy_size_t ); +void yysdpfree (void * ); + +#define yy_new_buffer yysdp_create_buffer + +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yysdpensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yysdp_create_buffer(yysdpin,YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } + +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yysdpensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yysdp_create_buffer(yysdpin,YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } + +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +#define yysdpwrap(n) 1 +#define YY_SKIP_YYWRAP + +typedef unsigned char YY_CHAR; + +FILE *yysdpin = (FILE *) 0, *yysdpout = (FILE *) 0; + +typedef int yy_state_type; + +extern int yysdplineno; +extern char *yysdptext; +#define yytext_ptr yysdptext + +static yy_state_type yy_get_previous_state (void ); +static yy_state_type yy_try_NUL_trans (yy_state_type current_state ); +static int yy_get_next_buffer (void ); +static void yy_fatal_error (yyconst char msg[] ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yysdptext. + */ +#define YY_DO_BEFORE_ACTION \ + (yytext_ptr) = yy_bp; \ + yysdpleng = (size_t) (yy_cp - yy_bp); \ + (yy_hold_char) = *yy_cp; \ + *yy_cp = '\0'; \ + (yy_c_buf_p) = yy_cp; + +#define YY_NUM_RULES 27 +#define YY_END_OF_BUFFER 28 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static yyconst flex_int16_t yy_accept[51] = + { 0, + 0, 0, 0, 0, 0, 0, 0, 0, 28, 12, + 11, 10, 12, 8, 8, 8, 8, 8, 8, 8, + 8, 15, 14, 17, 15, 13, 23, 25, 26, 20, + 19, 22, 20, 18, 9, 8, 5, 7, 4, 6, + 2, 3, 1, 16, 13, 23, 24, 21, 18, 0 + } ; + +static yyconst flex_int32_t yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 5, 6, 6, 6, 5, 6, 7, 1, + 1, 7, 7, 1, 7, 7, 6, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 6, 6, 1, + 9, 1, 6, 6, 10, 11, 12, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 13, 11, 14, 11, + 11, 11, 15, 11, 11, 16, 11, 11, 11, 11, + 6, 1, 6, 6, 7, 6, 10, 11, 12, 11, + + 11, 11, 11, 11, 11, 11, 11, 11, 13, 11, + 14, 11, 11, 11, 15, 11, 11, 16, 11, 11, + 11, 11, 6, 6, 6, 7, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static yyconst flex_int32_t yy_meta[17] = + { 0, + 1, 1, 2, 2, 3, 4, 5, 5, 4, 5, + 5, 5, 5, 5, 5, 5 + } ; + +static yyconst flex_int16_t yy_base[58] = + { 0, + 0, 9, 24, 31, 4, 26, 39, 44, 87, 88, + 88, 88, 83, 0, 76, 75, 74, 73, 72, 71, + 65, 88, 88, 88, 68, 30, 0, 88, 34, 88, + 88, 88, 33, 0, 88, 0, 88, 88, 88, 88, + 88, 88, 88, 88, 23, 0, 88, 88, 0, 88, + 49, 54, 59, 64, 67, 72, 74 + } ; + +static yyconst flex_int16_t yy_def[58] = + { 0, + 51, 50, 52, 52, 53, 53, 54, 54, 50, 50, + 50, 50, 50, 55, 55, 55, 55, 55, 55, 55, + 55, 50, 50, 50, 50, 50, 56, 50, 50, 50, + 50, 50, 50, 57, 50, 55, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 56, 50, 50, 57, 0, + 50, 50, 50, 50, 50, 50, 50 + } ; + +static yyconst flex_int16_t yy_nxt[105] = + { 0, + 10, 11, 12, 13, 50, 10, 28, 29, 10, 10, + 11, 12, 13, 14, 10, 14, 14, 10, 15, 16, + 17, 18, 19, 20, 21, 23, 24, 25, 28, 29, + 45, 26, 23, 24, 25, 48, 47, 45, 26, 30, + 31, 32, 33, 30, 30, 31, 32, 33, 30, 14, + 14, 14, 14, 14, 22, 22, 22, 22, 22, 27, + 27, 27, 27, 27, 34, 34, 34, 34, 34, 36, + 44, 36, 46, 43, 46, 46, 46, 49, 49, 42, + 41, 40, 39, 38, 37, 35, 50, 9, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, + + 50, 50, 50, 50 + } ; + +static yyconst flex_int16_t yy_chk[105] = + { 0, + 1, 1, 1, 1, 0, 1, 5, 5, 1, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 3, 3, 3, 6, 6, + 45, 3, 4, 4, 4, 33, 29, 26, 4, 7, + 7, 7, 7, 7, 8, 8, 8, 8, 8, 51, + 51, 51, 51, 51, 52, 52, 52, 52, 52, 53, + 53, 53, 53, 53, 54, 54, 54, 54, 54, 55, + 25, 55, 56, 21, 56, 56, 56, 57, 57, 20, + 19, 18, 17, 16, 15, 13, 9, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, + + 50, 50, 50, 50 + } ; + +static yy_state_type yy_last_accepting_state; +static char *yy_last_accepting_cpos; + +extern int yysdp_flex_debug; +int yysdp_flex_debug = 0; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +char *yysdptext; +#line 1 "sdp_scanner.lxx" +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#line 20 "sdp_scanner.lxx" +#include +#include "sdp_parse_ctrl.h" +#include "sdp_parser.h" +#include "audits/memman.h" + +using namespace std; + + + +#line 516 "sdp_scanner.cxx" + +#define INITIAL 0 +#define C_NUM 1 +#define C_LINE 2 +#define C_SAFE 3 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +static int yy_init_globals (void ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yysdpwrap (void ); +#else +extern int yysdpwrap (void ); +#endif +#endif + + static void yyunput (int c,char *buf_ptr ); + +#ifndef yytext_ptr +static void yy_flex_strncpy (char *,yyconst char *,int ); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * ); +#endif + +#ifndef YY_NO_INPUT + +#ifdef __cplusplus +static int yyinput (void ); +#else +static int input (void ); +#endif + +#endif + + static int yy_start_stack_ptr = 0; + static int yy_start_stack_depth = 0; + static int *yy_start_stack = NULL; + + static void yy_push_state (int new_state ); + + static void yy_pop_state (void ); + + static int yy_top_state (void ); + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#define YY_READ_BUF_SIZE 8192 +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO (void) fwrite( yysdptext, yysdpleng, 1, yysdpout ) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + size_t n; \ + for ( n = 0; n < max_size && \ + (c = getc( yysdpin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yysdpin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = fread(buf, 1, max_size, yysdpin))==0 && ferror(yysdpin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yysdpin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg ) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yysdplex (void); + +#define YY_DECL int yysdplex (void) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yysdptext and yysdpleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK break; +#endif + +#define YY_RULE_SETUP \ + if ( yysdpleng > 0 ) \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = \ + (yysdptext[yysdpleng - 1] == '\n'); \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + register yy_state_type yy_current_state; + register char *yy_cp, *yy_bp; + register int yy_act; + +#line 41 "sdp_scanner.lxx" + + 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 */ +#line 695 "sdp_scanner.cxx" + + if ( !(yy_init) ) + { + (yy_init) = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! (yy_start) ) + (yy_start) = 1; /* first start state */ + + if ( ! yysdpin ) + yysdpin = stdin; + + if ( ! yysdpout ) + yysdpout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + yysdpensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yysdp_create_buffer(yysdpin,YY_BUF_SIZE ); + } + + yysdp_load_buffer_state( ); + } + + while ( 1 ) /* loops until end-of-file is reached */ + { + yy_cp = (yy_c_buf_p); + + /* Support of yysdptext. */ + *yy_cp = (yy_hold_char); + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = (yy_start); + yy_current_state += YY_AT_BOL(); +yy_match: + do + { + register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)]; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 51 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + ++yy_cp; + } + while ( yy_base[yy_current_state] != 88 ); + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + if ( yy_act == 0 ) + { /* have to back up */ + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + yy_act = yy_accept[yy_current_state]; + } + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = (yy_hold_char); + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + goto yy_find_action; + +case 1: +YY_RULE_SETUP +#line 50 "sdp_scanner.lxx" +{ return T_LINE_VERSION; } + YY_BREAK +case 2: +YY_RULE_SETUP +#line 51 "sdp_scanner.lxx" +{ return T_LINE_ORIGIN; } + YY_BREAK +case 3: +YY_RULE_SETUP +#line 52 "sdp_scanner.lxx" +{ return T_LINE_SESSION_NAME; } + YY_BREAK +case 4: +YY_RULE_SETUP +#line 53 "sdp_scanner.lxx" +{ return T_LINE_CONNECTION; } + YY_BREAK +case 5: +YY_RULE_SETUP +#line 54 "sdp_scanner.lxx" +{ return T_LINE_ATTRIBUTE; } + YY_BREAK +case 6: +YY_RULE_SETUP +#line 55 "sdp_scanner.lxx" +{ return T_LINE_MEDIA; } + YY_BREAK +case 7: +YY_RULE_SETUP +#line 56 "sdp_scanner.lxx" +{ return T_LINE_UNKNOWN; } + YY_BREAK +/* Token as define in RFC 3261 */ +case 8: +YY_RULE_SETUP +#line 59 "sdp_scanner.lxx" +{ yysdplval.yysdpt_str = new string(yysdptext); + MEMMAN_NEW(yysdplval.yysdpt_str); + return T_TOKEN; } + YY_BREAK +/* End of line */ +case 9: +/* rule 9 can match eol */ +YY_RULE_SETUP +#line 64 "sdp_scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 10: +/* rule 10 can match eol */ +YY_RULE_SETUP +#line 65 "sdp_scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 11: +YY_RULE_SETUP +#line 67 "sdp_scanner.lxx" +/* Skip white space */ + YY_BREAK +/* Single character token */ +case 12: +YY_RULE_SETUP +#line 70 "sdp_scanner.lxx" +{ return yysdptext[0]; } + YY_BREAK +/* Number */ +case 13: +YY_RULE_SETUP +#line 73 "sdp_scanner.lxx" +{ yysdplval.yysdpt_int = atoi(yysdptext); + return T_NUM; } + YY_BREAK +case 14: +YY_RULE_SETUP +#line 75 "sdp_scanner.lxx" +/* Skip white space */ + YY_BREAK +case 15: +YY_RULE_SETUP +#line 76 "sdp_scanner.lxx" +{ return yysdptext[0]; } + YY_BREAK +case 16: +/* rule 16 can match eol */ +YY_RULE_SETUP +#line 77 "sdp_scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 17: +/* rule 17 can match eol */ +YY_RULE_SETUP +#line 78 "sdp_scanner.lxx" +{ return T_CRLF; } + YY_BREAK +/* Safe */ +case 18: +YY_RULE_SETUP +#line 81 "sdp_scanner.lxx" +{ yysdplval.yysdpt_str = new string(yysdptext); + MEMMAN_NEW(yysdplval.yysdpt_str); + return T_SAFE; } + YY_BREAK +case 19: +YY_RULE_SETUP +#line 84 "sdp_scanner.lxx" +/* Skip white space */ + YY_BREAK +case 20: +YY_RULE_SETUP +#line 85 "sdp_scanner.lxx" +{ return yysdptext[0]; } + YY_BREAK +case 21: +/* rule 21 can match eol */ +YY_RULE_SETUP +#line 86 "sdp_scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 22: +/* rule 22 can match eol */ +YY_RULE_SETUP +#line 87 "sdp_scanner.lxx" +{ return T_CRLF; } + YY_BREAK +/* Get all text till end of line */ +case 23: +YY_RULE_SETUP +#line 90 "sdp_scanner.lxx" +{ yysdplval.yysdpt_str = new string(yysdptext); + MEMMAN_NEW(yysdplval.yysdpt_str); + return T_LINE; } + YY_BREAK +case 24: +/* rule 24 can match eol */ +YY_RULE_SETUP +#line 93 "sdp_scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 25: +/* rule 25 can match eol */ +YY_RULE_SETUP +#line 94 "sdp_scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 26: +YY_RULE_SETUP +#line 95 "sdp_scanner.lxx" +{ return T_CRLF; } + YY_BREAK +case 27: +YY_RULE_SETUP +#line 96 "sdp_scanner.lxx" +ECHO; + YY_BREAK +#line 935 "sdp_scanner.cxx" +case YY_STATE_EOF(INITIAL): +case YY_STATE_EOF(C_NUM): +case YY_STATE_EOF(C_LINE): +case YY_STATE_EOF(C_SAFE): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = (yy_hold_char); + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yysdpin at a new source and called + * yysdplex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yysdpin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state ); + + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++(yy_c_buf_p); + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = (yy_c_buf_p); + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_END_OF_FILE: + { + (yy_did_buffer_switch_on_eof) = 0; + + if ( yysdpwrap( ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yysdptext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = + (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + (yy_c_buf_p) = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)]; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ +} /* end of yysdplex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (void) +{ + register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + register char *source = (yytext_ptr); + register int number_to_move, i; + int ret_val; + + if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr)) - 1; + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER; + + int yy_c_buf_p_offset = + (int) ((yy_c_buf_p) - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yysdprealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = 0; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + (yy_n_chars), num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + if ( (yy_n_chars) == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yysdprestart(yysdpin ); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + (yy_n_chars) += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR; + + (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (void) +{ + register yy_state_type yy_current_state; + register char *yy_cp; + + yy_current_state = (yy_start); + yy_current_state += YY_AT_BOL(); + + for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) + { + register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 51 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state ) +{ + register int yy_is_jam; + register char *yy_cp = (yy_c_buf_p); + + register YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 51 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + yy_is_jam = (yy_current_state == 50); + + return yy_is_jam ? 0 : yy_current_state; +} + + static void yyunput (int c, register char * yy_bp ) +{ + register char *yy_cp; + + yy_cp = (yy_c_buf_p); + + /* undo effects of setting up yysdptext */ + *yy_cp = (yy_hold_char); + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + { /* need to shift things up to make room */ + /* +2 for EOB chars. */ + register int number_to_move = (yy_n_chars) + 2; + register char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2]; + register char *source = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]; + + while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + *--dest = *--source; + + yy_cp += (int) (dest - source); + yy_bp += (int) (dest - source); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_buf_size; + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + YY_FATAL_ERROR( "flex scanner push-back overflow" ); + } + + *--yy_cp = (char) c; + + (yytext_ptr) = yy_bp; + (yy_hold_char) = *yy_cp; + (yy_c_buf_p) = yy_cp; +} + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (void) +#else + static int input (void) +#endif + +{ + int c; + + *(yy_c_buf_p) = (yy_hold_char); + + if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + /* This was really a NUL. */ + *(yy_c_buf_p) = '\0'; + + else + { /* need more input */ + int offset = (yy_c_buf_p) - (yytext_ptr); + ++(yy_c_buf_p); + + switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yysdprestart(yysdpin ); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yysdpwrap( ) ) + return EOF; + + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(); +#else + return input(); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = (yytext_ptr) + offset; + break; + } + } + } + + c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */ + *(yy_c_buf_p) = '\0'; /* preserve yysdptext */ + (yy_hold_char) = *++(yy_c_buf_p); + + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = (c == '\n'); + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * + * @note This function does not reset the start condition to @c INITIAL . + */ + void yysdprestart (FILE * input_file ) +{ + + if ( ! YY_CURRENT_BUFFER ){ + yysdpensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yysdp_create_buffer(yysdpin,YY_BUF_SIZE ); + } + + yysdp_init_buffer(YY_CURRENT_BUFFER,input_file ); + yysdp_load_buffer_state( ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * + */ + void yysdp_switch_to_buffer (YY_BUFFER_STATE new_buffer ) +{ + + /* TODO. We should be able to replace this entire function body + * with + * yysdppop_buffer_state(); + * yysdppush_buffer_state(new_buffer); + */ + yysdpensure_buffer_stack (); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yysdp_load_buffer_state( ); + + /* We don't actually know whether we did this switch during + * EOF (yysdpwrap()) processing, but the only time this flag + * is looked at is after yysdpwrap() is called, so it's safe + * to go ahead and always set it. + */ + (yy_did_buffer_switch_on_eof) = 1; +} + +static void yysdp_load_buffer_state (void) +{ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yysdpin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + (yy_hold_char) = *(yy_c_buf_p); +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * + * @return the allocated buffer state. + */ + YY_BUFFER_STATE yysdp_create_buffer (FILE * file, int size ) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yysdpalloc(sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yysdp_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yysdpalloc(b->yy_buf_size + 2 ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yysdp_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yysdp_init_buffer(b,file ); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yysdp_create_buffer() + * + */ + void yysdp_delete_buffer (YY_BUFFER_STATE b ) +{ + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yysdpfree((void *) b->yy_ch_buf ); + + yysdpfree((void *) b ); +} + +#ifndef __cplusplus +extern int isatty (int ); +#endif /* __cplusplus */ + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yysdprestart() or at EOF. + */ + static void yysdp_init_buffer (YY_BUFFER_STATE b, FILE * file ) + +{ + int oerrno = errno; + + yysdp_flush_buffer(b ); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yysdp_init_buffer was _probably_ + * called from yysdprestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * + */ + void yysdp_flush_buffer (YY_BUFFER_STATE b ) +{ + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yysdp_load_buffer_state( ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * + */ +void yysdppush_buffer_state (YY_BUFFER_STATE new_buffer ) +{ + if (new_buffer == NULL) + return; + + yysdpensure_buffer_stack(); + + /* This block is copied from yysdp_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + (yy_buffer_stack_top)++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yysdp_switch_to_buffer. */ + yysdp_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * + */ +void yysdppop_buffer_state (void) +{ + if (!YY_CURRENT_BUFFER) + return; + + yysdp_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + if ((yy_buffer_stack_top) > 0) + --(yy_buffer_stack_top); + + if (YY_CURRENT_BUFFER) { + yysdp_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void yysdpensure_buffer_stack (void) +{ + int num_to_alloc; + + if (!(yy_buffer_stack)) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; + (yy_buffer_stack) = (struct yy_buffer_state**)yysdpalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + ); + + memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + (yy_buffer_stack_max) = num_to_alloc; + (yy_buffer_stack_top) = 0; + return; + } + + if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + int grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = (yy_buffer_stack_max) + grow_size; + (yy_buffer_stack) = (struct yy_buffer_state**)yysdprealloc + ((yy_buffer_stack), + num_to_alloc * sizeof(struct yy_buffer_state*) + ); + + /* zero only the new slots.*/ + memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*)); + (yy_buffer_stack_max) = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yysdp_scan_buffer (char * base, yy_size_t size ) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return 0; + + b = (YY_BUFFER_STATE) yysdpalloc(sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yysdp_scan_buffer()" ); + + b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = 0; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yysdp_switch_to_buffer(b ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to yysdplex() will + * scan from a @e copy of @a str. + * @param str a NUL-terminated string to scan + * + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * yysdp_scan_bytes() instead. + */ +YY_BUFFER_STATE yysdp_scan_string (yyconst char * yystr ) +{ + + return yysdp_scan_bytes(yystr,strlen(yystr) ); +} + +/** Setup the input buffer state to scan the given bytes. The next call to yysdplex() will + * scan from a @e copy of @a bytes. + * @param bytes the byte buffer to scan + * @param len the number of bytes in the buffer pointed to by @a bytes. + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yysdp_scan_bytes (yyconst char * yybytes, int _yybytes_len ) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = _yybytes_len + 2; + buf = (char *) yysdpalloc(n ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yysdp_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = yysdp_scan_buffer(buf,n ); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yysdp_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + + static void yy_push_state (int new_state ) +{ + if ( (yy_start_stack_ptr) >= (yy_start_stack_depth) ) + { + yy_size_t new_size; + + (yy_start_stack_depth) += YY_START_STACK_INCR; + new_size = (yy_start_stack_depth) * sizeof( int ); + + if ( ! (yy_start_stack) ) + (yy_start_stack) = (int *) yysdpalloc(new_size ); + + else + (yy_start_stack) = (int *) yysdprealloc((void *) (yy_start_stack),new_size ); + + if ( ! (yy_start_stack) ) + YY_FATAL_ERROR( + "out of memory expanding start-condition stack" ); + } + + (yy_start_stack)[(yy_start_stack_ptr)++] = YY_START; + + BEGIN(new_state); +} + + static void yy_pop_state (void) +{ + if ( --(yy_start_stack_ptr) < 0 ) + YY_FATAL_ERROR( "start-condition stack underflow" ); + + BEGIN((yy_start_stack)[(yy_start_stack_ptr)]); +} + + static int yy_top_state (void) +{ + return (yy_start_stack)[(yy_start_stack_ptr) - 1]; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yy_fatal_error (yyconst char* msg ) +{ + (void) fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yysdptext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yysdptext[yysdpleng] = (yy_hold_char); \ + (yy_c_buf_p) = yysdptext + yyless_macro_arg; \ + (yy_hold_char) = *(yy_c_buf_p); \ + *(yy_c_buf_p) = '\0'; \ + yysdpleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the input stream. + * + */ +FILE *yysdpget_in (void) +{ + return yysdpin; +} + +/** Get the output stream. + * + */ +FILE *yysdpget_out (void) +{ + return yysdpout; +} + +/** Get the length of the current token. + * + */ +int yysdpget_leng (void) +{ + return yysdpleng; +} + +/** Get the current token. + * + */ + +char *yysdpget_text (void) +{ + return yysdptext; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param in_str A readable stream. + * + * @see yysdp_switch_to_buffer + */ +void yysdpset_in (FILE * in_str ) +{ + yysdpin = in_str ; +} + +void yysdpset_out (FILE * out_str ) +{ + yysdpout = out_str ; +} + +int yysdpget_debug (void) +{ + return yysdp_flex_debug; +} + +void yysdpset_debug (int bdebug ) +{ + yysdp_flex_debug = bdebug ; +} + +static int yy_init_globals (void) +{ + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from yysdplex_destroy(), so don't allocate here. + */ + + (yy_buffer_stack) = 0; + (yy_buffer_stack_top) = 0; + (yy_buffer_stack_max) = 0; + (yy_c_buf_p) = (char *) 0; + (yy_init) = 0; + (yy_start) = 0; + + (yy_start_stack_ptr) = 0; + (yy_start_stack_depth) = 0; + (yy_start_stack) = NULL; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yysdpin = stdin; + yysdpout = stdout; +#else + yysdpin = (FILE *) 0; + yysdpout = (FILE *) 0; +#endif + + /* For future reference: Set errno on error, since we are called by + * yysdplex_init() + */ + return 0; +} + +/* yysdplex_destroy is for both reentrant and non-reentrant scanners. */ +int yysdplex_destroy (void) +{ + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yysdp_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yysdppop_buffer_state(); + } + + /* Destroy the stack itself. */ + yysdpfree((yy_buffer_stack) ); + (yy_buffer_stack) = NULL; + + /* Destroy the start condition stack. */ + yysdpfree((yy_start_stack) ); + (yy_start_stack) = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * yysdplex() is called, initialization will occur. */ + yy_init_globals( ); + + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, yyconst char * s2, int n ) +{ + register int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * s ) +{ + register int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *yysdpalloc (yy_size_t size ) +{ + return (void *) malloc( size ); +} + +void *yysdprealloc (void * ptr, yy_size_t size ) +{ + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return (void *) realloc( (char *) ptr, size ); +} + +void yysdpfree (void * ptr ) +{ + free( (char *) ptr ); /* see yysdprealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#line 96 "sdp_scanner.lxx" diff --git a/src/sdp/sdp_scanner.lxx b/src/sdp/sdp_scanner.lxx new file mode 100644 index 0000000..5ea9ac3 --- /dev/null +++ b/src/sdp/sdp_scanner.lxx @@ -0,0 +1,95 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +%{ +#include +#include "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; } diff --git a/src/sender.cpp b/src/sender.cpp new file mode 100644 index 0000000..d90ad42 --- /dev/null +++ b/src/sender.cpp @@ -0,0 +1,561 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/src/sender.h b/src/sender.h new file mode 100644 index 0000000..7c78e95 --- /dev/null +++ b/src/sender.h @@ -0,0 +1,33 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/sequence_number.h b/src/sequence_number.h new file mode 100644 index 0000000..c192012 --- /dev/null +++ b/src/sequence_number.h @@ -0,0 +1,148 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @file + * Sequence number operations. + */ + +#ifndef _SEQUENCE_NUMBER_H +#define _SEQUENCE_NUMBER_H + +#include + +/** + * 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 diff --git a/src/service.cpp b/src/service.cpp new file mode 100644 index 0000000..e292125 --- /dev/null +++ b/src/service.cpp @@ -0,0 +1,400 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include "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; +} diff --git a/src/service.h b/src/service.h new file mode 100644 index 0000000..99f76d7 --- /dev/null +++ b/src/service.h @@ -0,0 +1,98 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/session.cpp b/src/session.cpp new file mode 100644 index 0000000..83771f6 --- /dev/null +++ b/src/session.cpp @@ -0,0 +1,822 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include "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_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[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); +} diff --git a/src/session.h b/src/session.h new file mode 100644 index 0000000..2ef9cdc --- /dev/null +++ b/src/session.h @@ -0,0 +1,211 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/sockets/Makefile.am b/src/sockets/Makefile.am new file mode 100644 index 0000000..fdcfce4 --- /dev/null +++ b/src/sockets/Makefile.am @@ -0,0 +1,30 @@ +AM_CPPFLAGS = \ + -Wall \ + -I$(top_srcdir)/src\ + $(XML2_CFLAGS) + +noinst_LIBRARIES = libsocket.a + +#noinst_PROGRAMS = srvinfo urlinfo +#srvinfo_SOURCES = srvinfo.cpp +#srvinfo_LDADD = $(top_builddir)/src/util.o\ +# $(top_builddir)/src/sockets/libsocket.a\ +# -lresolv +#urlinfo_SOURCES = urlinfo.cpp +#urlinfo_LDADD = $(top_builddir)/src/util.o\ +# $(top_builddir)/src/sockets/libsocket.a\ +# -lresolv + +libsocket_a_SOURCES =\ + connection.cpp\ + connection_table.cpp\ + dnssrv.cpp\ + interfaces.cpp\ + socket.cpp\ + url.cpp\ + connection.h\ + connection_table.h\ + dnssrv.h\ + interfaces.h\ + socket.h\ + url.h diff --git a/src/sockets/connection.cpp b/src/sockets/connection.cpp new file mode 100644 index 0000000..6d5486a --- /dev/null +++ b/src/sockets/connection.cpp @@ -0,0 +1,294 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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(); +} diff --git a/src/sockets/connection.h b/src/sockets/connection.h new file mode 100644 index 0000000..b907206 --- /dev/null +++ b/src/sockets/connection.h @@ -0,0 +1,232 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/sockets/connection_table.cpp b/src/sockets/connection_table.cpp new file mode 100644 index 0000000..4211eb5 --- /dev/null +++ b/src/sockets/connection_table.cpp @@ -0,0 +1,411 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +}; diff --git a/src/sockets/connection_table.h b/src/sockets/connection_table.h new file mode 100644 index 0000000..81a4894 --- /dev/null +++ b/src/sockets/connection_table.h @@ -0,0 +1,170 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/sockets/dnssrv.cpp b/src/sockets/dnssrv.cpp new file mode 100644 index 0000000..38cd61d --- /dev/null +++ b/src/sockets/dnssrv.cpp @@ -0,0 +1,176 @@ +/* + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/sockets/interfaces.cpp b/src/sockets/interfaces.cpp new file mode 100644 index 0000000..293c48d --- /dev/null +++ b/src/sockets/interfaces.cpp @@ -0,0 +1,114 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +#include "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; +} diff --git a/src/sockets/interfaces.h b/src/sockets/interfaces.h new file mode 100644 index 0000000..c8cc633 --- /dev/null +++ b/src/sockets/interfaces.h @@ -0,0 +1,56 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/sockets/socket.cpp b/src/sockets/socket.cpp new file mode 100644 index 0000000..87bdc27 --- /dev/null +++ b/src/sockets/socket.cpp @@ -0,0 +1,444 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include "twinkle_config.h" +#include "socket.h" +#include "audits/memman.h" + +#if HAVE_UNISTD_H +#include +#endif + +#if HAVE_LINUX_TYPES_H +#include +#endif + +#if 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) { +#if 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) { +#if 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); +} diff --git a/src/sockets/socket.h b/src/sockets/socket.h new file mode 100644 index 0000000..0f0f183 --- /dev/null +++ b/src/sockets/socket.h @@ -0,0 +1,222 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/sockets/url.cpp b/src/sockets/url.cpp new file mode 100644 index 0000000..0b10dd0 --- /dev/null +++ b/src/sockets/url.cpp @@ -0,0 +1,911 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/src/sockets/url.h b/src/sockets/url.h new file mode 100644 index 0000000..eb0ee2d --- /dev/null +++ b/src/sockets/url.h @@ -0,0 +1,279 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/stun/Makefile.am b/src/stun/Makefile.am new file mode 100644 index 0000000..f52face --- /dev/null +++ b/src/stun/Makefile.am @@ -0,0 +1,15 @@ +AM_CPPFLAGS = \ + -Wall \ + -I$(top_srcdir)/src \ + $(CCRTP_CFLAGS)\ + $(XML2_CFLAGS) + +noinst_LIBRARIES = libstun.a + +libstun_a_SOURCES =\ + stun.cxx\ + stun_transaction.cpp\ + udp.cxx\ + stun.h\ + stun_transaction.h\ + udp.h diff --git a/src/stun/stun.cxx b/src/stun/stun.cxx new file mode 100644 index 0000000..f5e9bc9 --- /dev/null +++ b/src/stun/stun.cxx @@ -0,0 +1,2587 @@ +#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 Addres = "; + s += h_ip2str(m.mappedAddress.ipv4.addr); + s += ':'; + s += int2str(m.mappedAddress.ipv4.port); + s += "\n"; + } + + + if (m.hasResponseAddress) { + s += "Response Addres = "; + 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 Addres = "; + s += h_ip2str(m.sourceAddress.ipv4.addr); + s += ':'; + s += int2str(m.sourceAddress.ipv4.port); + s += "\n"; + } + + if (m.hasChangedAddress) { + s += "Changed Addres = "; + 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 unkown\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: + + diff --git a/src/stun/stun.h b/src/stun/stun.h new file mode 100644 index 0000000..1746ce5 --- /dev/null +++ b/src/stun/stun.h @@ -0,0 +1,405 @@ +#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: + diff --git a/src/stun/stun_transaction.cpp b/src/stun/stun_transaction.cpp new file mode 100644 index 0000000..9514ba4 --- /dev/null +++ b/src/stun/stun_transaction.cpp @@ -0,0 +1,680 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include "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; + } +} + + diff --git a/src/stun/stun_transaction.h b/src/stun/stun_transaction.h new file mode 100644 index 0000000..c06e1c5 --- /dev/null +++ b/src/stun/stun_transaction.h @@ -0,0 +1,159 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/stun/udp.cxx b/src/stun/udp.cxx new file mode 100644 index 0000000..7a75134 --- /dev/null +++ b/src/stun/udp.cxx @@ -0,0 +1,349 @@ +#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: diff --git a/src/stun/udp.h b/src/stun/udp.h new file mode 100644 index 0000000..13f8353 --- /dev/null +++ b/src/stun/udp.h @@ -0,0 +1,156 @@ +#ifndef udp_h +#define udp_h + + +#ifdef __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 diff --git a/src/sub_refer.cpp b/src/sub_refer.cpp new file mode 100644 index 0000000..f520575 --- /dev/null +++ b/src/sub_refer.cpp @@ -0,0 +1,324 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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 succesful 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; +} diff --git a/src/sub_refer.h b/src/sub_refer.h new file mode 100644 index 0000000..6774779 --- /dev/null +++ b/src/sub_refer.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 diff --git a/src/subscription.cpp b/src/subscription.cpp new file mode 100644 index 0000000..8429618 --- /dev/null +++ b/src/subscription.cpp @@ -0,0 +1,695 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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 succesful.\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); +} diff --git a/src/subscription.h b/src/subscription.h new file mode 100644 index 0000000..67359a0 --- /dev/null +++ b/src/subscription.h @@ -0,0 +1,319 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 succesful. 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 diff --git a/src/subscription_dialog.cpp b/src/subscription_dialog.cpp new file mode 100644 index 0000000..68b6292 --- /dev/null +++ b/src/subscription_dialog.cpp @@ -0,0 +1,409 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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(); +} diff --git a/src/subscription_dialog.h b/src/subscription_dialog.h new file mode 100644 index 0000000..9c1172c --- /dev/null +++ b/src/subscription_dialog.h @@ -0,0 +1,170 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/sys_settings.cpp b/src/sys_settings.cpp new file mode 100644 index 0000000..36e7142 --- /dev/null +++ b/src/sys_settings.cpp @@ -0,0 +1,2048 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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" + +// 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 = atoi(v[0].c_str()); + y = atoi(v[1].c_str()); + width = atoi(v[2].c_str()); + height = atoi(v[3].c_str()); + } +} + +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; + + 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; +} + +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_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-2009 "; + s += PRODUCT_AUTHOR; + if (html) s += "
"; + s += "\n"; + + s += "http://www.twinklephone.com"; + 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 + + 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 CommonC++ - http://www.gnu.org/software/commoncpp"; + 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 = atoi(l[1].c_str()); + t.tm_year = atoi(l[2].c_str()) - 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_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_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 << 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; +} diff --git a/src/sys_settings.h b/src/sys_settings.h new file mode 100644 index 0000000..0a5f288 --- /dev/null +++ b/src/sys_settings.h @@ -0,0 +1,548 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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; + + /** 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_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_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 succesfully. + * @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 diff --git a/src/threads/Makefile.am b/src/threads/Makefile.am new file mode 100644 index 0000000..221bddf --- /dev/null +++ b/src/threads/Makefile.am @@ -0,0 +1,11 @@ +AM_CPPFLAGS = -Wall -I$(top_srcdir)/src + +noinst_LIBRARIES = libthread.a + +libthread_a_SOURCES =\ + thread.cpp\ + mutex.cpp\ + sema.cpp\ + thread.h\ + mutex.h\ + sema.h diff --git a/src/threads/mutex.cpp b/src/threads/mutex.cpp new file mode 100644 index 0000000..0ae2a99 --- /dev/null +++ b/src/threads/mutex.cpp @@ -0,0 +1,90 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include "mutex.h" +#include "thread.h" + +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(); +} diff --git a/src/threads/mutex.h b/src/threads/mutex.h new file mode 100644 index 0000000..9970e08 --- /dev/null +++ b/src/threads/mutex.h @@ -0,0 +1,86 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _H_MUTEX +#define _H_MUTEX + +#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(); +}; + +#endif diff --git a/src/threads/sema.cpp b/src/threads/sema.cpp new file mode 100644 index 0000000..8eb686b --- /dev/null +++ b/src/threads/sema.cpp @@ -0,0 +1,76 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include "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); +} diff --git a/src/threads/sema.h b/src/threads/sema.h new file mode 100644 index 0000000..0cd04a5 --- /dev/null +++ b/src/threads/sema.h @@ -0,0 +1,44 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/threads/thread.cpp b/src/threads/thread.cpp new file mode 100644 index 0000000..30fa2fa --- /dev/null +++ b/src/threads/thread.cpp @@ -0,0 +1,108 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include "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); +} + diff --git a/src/threads/thread.h b/src/threads/thread.h new file mode 100644 index 0000000..f839a25 --- /dev/null +++ b/src/threads/thread.h @@ -0,0 +1,59 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/timekeeper.cpp b/src/timekeeper.cpp new file mode 100644 index 0000000..9a1a515 --- /dev/null +++ b/src/timekeeper.cpp @@ -0,0 +1,785 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include "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(); + } +} diff --git a/src/timekeeper.h b/src/timekeeper.h new file mode 100644 index 0000000..4ed2565 --- /dev/null +++ b/src/timekeeper.h @@ -0,0 +1,267 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/transaction.cpp b/src/transaction.cpp new file mode 100644 index 0000000..30ea6dd --- /dev/null +++ b/src/transaction.cpp @@ -0,0 +1,1307 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include "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; + } +} diff --git a/src/transaction.h b/src/transaction.h new file mode 100644 index 0000000..3b10eee --- /dev/null +++ b/src/transaction.h @@ -0,0 +1,372 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/transaction_layer.cpp b/src/transaction_layer.cpp new file mode 100644 index 0000000..d298261 --- /dev/null +++ b/src/transaction_layer.cpp @@ -0,0 +1,315 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include "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(); +} diff --git a/src/transaction_layer.h b/src/transaction_layer.h new file mode 100644 index 0000000..ee2b62a --- /dev/null +++ b/src/transaction_layer.h @@ -0,0 +1,123 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/transaction_mgr.cpp b/src/transaction_mgr.cpp new file mode 100644 index 0000000..4ffaa5c --- /dev/null +++ b/src/transaction_mgr.cpp @@ -0,0 +1,732 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include "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; +} diff --git a/src/transaction_mgr.h b/src/transaction_mgr.h new file mode 100644 index 0000000..05794ef --- /dev/null +++ b/src/transaction_mgr.h @@ -0,0 +1,101 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/translator.h b/src/translator.h new file mode 100644 index 0000000..aeb0267 --- /dev/null +++ b/src/translator.h @@ -0,0 +1,49 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/user.cpp b/src/user.cpp new file mode 100644 index 0000000..224e291 --- /dev/null +++ b/src/user.cpp @@ -0,0 +1,3147 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#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); + } +} + +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); + } +} + +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); + } +} + +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); + } +} + +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 (boost::bad_expression) { + // 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); + 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); + } 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; + 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.str(), ','); + 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++) + { + boost::smatch m; + + try { + if (boost::regex_match(number, m, i->re)) { + string result = m.format(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); +} diff --git a/src/user.h b/src/user.h new file mode 100644 index 0000000..94c015a --- /dev/null +++ b/src/user.h @@ -0,0 +1,850 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// 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 +#include "protocol.h" +#include "sys_settings.h" +#include "audio/audio_codecs.h" +#include "sockets/url.h" +#include "threads/mutex.h" +#include "boost/regex.hpp" + +// 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 { + boost::regex re; + string fmt; + + string str(void) const { return re.str() + " --> " + 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 diff --git a/src/userintf.cpp b/src/userintf.cpp new file mode 100644 index 0000000..0814bcb --- /dev/null +++ b/src/userintf.cpp @@ -0,0 +1,3511 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include "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 (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"; + 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-2009 " << 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::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; + 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, char dtmf_event) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": DTMF detected: "; + + if (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, char 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, char dtmf_event) { + // No feed back in CLI +} + +void t_userintf::cb_async_send_dtmf(int line, char 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 succesfully 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 << "Tranfer 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; +} diff --git a/src/userintf.h b/src/userintf.h new file mode 100644 index 0000000..f29ce68 --- /dev/null +++ b/src/userintf.h @@ -0,0 +1,446 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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" + +// 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, char dtmf_event); + virtual void cb_async_dtmf_detected(int line, char dtmf_event); + virtual void cb_send_dtmf(int line, char dtmf_event); + virtual void cb_async_send_dtmf(int line, char 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); + +}; + +void *process_events_main(void *arg); + +extern t_userintf *ui; + +#endif diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..a88e85d --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,765 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..b2509df --- /dev/null +++ b/src/util.h @@ -0,0 +1,273 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am new file mode 100644 index 0000000..e4d8fb5 --- /dev/null +++ b/src/utils/Makefile.am @@ -0,0 +1,11 @@ +AM_CPPFLAGS = -Wall $(XML2_CFLAGS) -I$(top_srcdir)/src + +noinst_LIBRARIES = libutils.a + +libutils_a_SOURCES =\ + file_utils.cpp\ + mime_database.cpp\ + file_utils.h\ + mime_database.h\ + record_file.h\ + record_file.hpp diff --git a/src/utils/file_utils.cpp b/src/utils/file_utils.cpp new file mode 100644 index 0000000..e3734c1 --- /dev/null +++ b/src/utils/file_utils.cpp @@ -0,0 +1,128 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/utils/file_utils.h b/src/utils/file_utils.h new file mode 100644 index 0000000..a6e97a1 --- /dev/null +++ b/src/utils/file_utils.h @@ -0,0 +1,82 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/utils/mime_database.cpp b/src/utils/mime_database.cpp new file mode 100644 index 0000000..ff8248e --- /dev/null +++ b/src/utils/mime_database.cpp @@ -0,0 +1,104 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "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; +} diff --git a/src/utils/mime_database.h b/src/utils/mime_database.h new file mode 100644 index 0000000..fcf6444 --- /dev/null +++ b/src/utils/mime_database.h @@ -0,0 +1,90 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 diff --git a/src/utils/record_file.h b/src/utils/record_file.h new file mode 100644 index 0000000..6108ea0 --- /dev/null +++ b/src/utils/record_file.h @@ -0,0 +1,158 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @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 succesfully 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 succesfully 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 succesfully. + * @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 succesfully. + * @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 diff --git a/src/utils/record_file.hpp b/src/utils/record_file.hpp new file mode 100644 index 0000000..43281d6 --- /dev/null +++ b/src/utils/record_file.hpp @@ -0,0 +1,178 @@ +/* + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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; +} diff --git a/twinkle.desktop.in b/twinkle.desktop.in new file mode 100644 index 0000000..7e394e9 --- /dev/null +++ b/twinkle.desktop.in @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Twinkle +GenericName=A SIP softphone +Comment=A SIP softphone +Type=Application +Exec=twinkle +Icon=@datadir@/twinkle48.png +StartupNotify=true +Terminal=false +Categories=Qt;KDE;Network;Telephony; diff --git a/twinkle.spec.in b/twinkle.spec.in new file mode 100644 index 0000000..ba89eaf --- /dev/null +++ b/twinkle.spec.in @@ -0,0 +1,70 @@ +Summary: 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: commoncpp2 >= 1.6.0 +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: commoncpp2-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 + -- cgit v1.2.3